Compare commits
443 Commits
v2.0.3
...
@payloadcm
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
27589482dd | ||
|
|
d7ab4b7062 | ||
|
|
2c8fbf1be3 | ||
|
|
eec88f8f1b | ||
|
|
1481ef97b5 | ||
|
|
a89e89fb80 | ||
|
|
7e7eeb059d | ||
|
|
dc2a502dcc | ||
|
|
a9a5ba82d8 | ||
|
|
e6e8fae1c5 | ||
|
|
a05868a7f3 | ||
|
|
f27cd26575 | ||
|
|
db835ea5c8 | ||
|
|
d8f265fb94 | ||
|
|
d81d4eb075 | ||
|
|
52f89c0136 | ||
|
|
b0083b7c07 | ||
|
|
21649537a6 | ||
|
|
9be34c9599 | ||
|
|
8ca632e541 | ||
|
|
2ef79145a4 | ||
|
|
a0641a445d | ||
|
|
3a2e78f7f3 | ||
|
|
976d69d154 | ||
|
|
66018362fe | ||
|
|
647fe23d1c | ||
|
|
d7c61861f6 | ||
|
|
7caa098023 | ||
|
|
fd54c40400 | ||
|
|
e180131314 | ||
|
|
5902d4542b | ||
|
|
6bc282444e | ||
|
|
4dc6c09347 | ||
|
|
03b9ab0054 | ||
|
|
3c3c93f483 | ||
|
|
5dbfb1a335 | ||
|
|
d411874589 | ||
|
|
8358e2f2d2 | ||
|
|
2c67eff059 | ||
|
|
012b8e6f90 | ||
|
|
fcd4c8d830 | ||
|
|
81ec435363 | ||
|
|
e116fcfbf5 | ||
|
|
c47632dc1d | ||
|
|
0dab68b336 | ||
|
|
483f93bfcf | ||
|
|
4bd01df411 | ||
|
|
c956a85252 | ||
|
|
beed83b231 | ||
|
|
3b1bdcbe41 | ||
|
|
d3d0971275 | ||
|
|
1a99d66cd0 | ||
|
|
52c4a63bf1 | ||
|
|
3446d28602 | ||
|
|
2eb18771a1 | ||
|
|
f6fd5d6742 | ||
|
|
76d6c88261 | ||
|
|
10ebd76fcf | ||
|
|
36d6eb0a69 | ||
|
|
cd1f8dc332 | ||
|
|
e4275aa228 | ||
|
|
ddfcb2f12e | ||
|
|
09f33eae2c | ||
|
|
d3b7c9feec | ||
|
|
5fd3d43000 | ||
|
|
7767679caa | ||
|
|
fdf2e32005 | ||
|
|
773be8744d | ||
|
|
40d5bc0c4a | ||
|
|
fd33c790f2 | ||
|
|
ae7aac7639 | ||
|
|
05cc2873b4 | ||
|
|
171ee121e9 | ||
|
|
4af1d7d812 | ||
|
|
d229fc391a | ||
|
|
46a24a9822 | ||
|
|
06a51b3c9b | ||
|
|
1d4142ccc0 | ||
|
|
f1741beba2 | ||
|
|
9103277a10 | ||
|
|
d84673f400 | ||
|
|
15e23a3adc | ||
|
|
565929adcf | ||
|
|
8bbac60e60 | ||
|
|
15c7f0dbf3 | ||
|
|
05eba56d7d | ||
|
|
ab984b3ea9 | ||
|
|
a54638eb47 | ||
|
|
69af8d9c83 | ||
|
|
64864686c4 | ||
|
|
32c0bef05e | ||
|
|
a77513e94f | ||
|
|
aaf883909c | ||
|
|
cc56da11d6 | ||
|
|
41d9c28073 | ||
|
|
a071b97607 | ||
|
|
cfd9231403 | ||
|
|
71dce62646 | ||
|
|
db376f24ba | ||
|
|
2752483ac7 | ||
|
|
860f867c62 | ||
|
|
5b0adbe9c3 | ||
|
|
fb7d1be2f3 | ||
|
|
687a2e85d0 | ||
|
|
df80483afe | ||
|
|
8781770d83 | ||
|
|
ed1d5a60f7 | ||
|
|
50a0965561 | ||
|
|
c31fa5dd83 | ||
|
|
440eb8d9c6 | ||
|
|
1523b2be41 | ||
|
|
68f55c4064 | ||
|
|
0d3544ea04 | ||
|
|
f152f451dc | ||
|
|
5d92436e39 | ||
|
|
63edecddd8 | ||
|
|
65cdf6bfd0 | ||
|
|
9e831a6a00 | ||
|
|
0b64c5fb66 | ||
|
|
40426a25df | ||
|
|
c91b1e8310 | ||
|
|
06e2fa9d11 | ||
|
|
72249d1ecd | ||
|
|
dc22496103 | ||
|
|
c6925ec29f | ||
|
|
8f46b31249 | ||
|
|
af1c2e924e | ||
|
|
1487250752 | ||
|
|
e3c776523a | ||
|
|
c09e9d96cf | ||
|
|
aabc0650f8 | ||
|
|
4dd4e9aaae | ||
|
|
3b63b7fc3c | ||
|
|
f0e2e78b82 | ||
|
|
a154adf066 | ||
|
|
d86bcc1495 | ||
|
|
2b7043c6e6 | ||
|
|
e0afeeca97 | ||
|
|
76e306ddd8 | ||
|
|
cfc78ed4f5 | ||
|
|
64b0db5a7a | ||
|
|
48600f0c66 | ||
|
|
6d2dd5849d | ||
|
|
6d9353b53f | ||
|
|
1914be75aa | ||
|
|
b17f627e02 | ||
|
|
bef79621ee | ||
|
|
af892ecb0e | ||
|
|
a42e84bbb2 | ||
|
|
470bdb72ff | ||
|
|
5c36be949c | ||
|
|
eb97acd408 | ||
|
|
2567ac58ba | ||
|
|
9ff014bbfe | ||
|
|
e6f0d35985 | ||
|
|
b1e449e005 | ||
|
|
9ae585d23c | ||
|
|
9de3320933 | ||
|
|
5d429fa7ae | ||
|
|
dc8f1925f0 | ||
|
|
15f650afde | ||
|
|
c945384d63 | ||
|
|
dfada1b238 | ||
|
|
229bdda2c1 | ||
|
|
a1d51fb410 | ||
|
|
46430f5598 | ||
|
|
e41899cd27 | ||
|
|
890af8be05 | ||
|
|
8bfae6b932 | ||
|
|
ace3e577f6 | ||
|
|
9198245ad9 | ||
|
|
f0095937ba | ||
|
|
0bbd7137cd | ||
|
|
ffed34cf27 | ||
|
|
1ebb9f3915 | ||
|
|
eab04d9b4d | ||
|
|
1758b6c449 | ||
|
|
bca1be8cb6 | ||
|
|
1e197933dd | ||
|
|
4eb929d57c | ||
|
|
198209c2a4 | ||
|
|
54f19ce1e3 | ||
|
|
d32f3ade1b | ||
|
|
bf189abc91 | ||
|
|
69a379e49f | ||
|
|
493fc3ed68 | ||
|
|
138e495e1a | ||
|
|
8fe619a221 | ||
|
|
5195a80dba | ||
|
|
909cf90fa2 | ||
|
|
c1d1a00d4a | ||
|
|
ae68093f35 | ||
|
|
0f2f355a01 | ||
|
|
0101aa60d9 | ||
|
|
c823ee07cd | ||
|
|
1f12f9b480 | ||
|
|
ec31ab3a2c | ||
|
|
a7bea35d69 | ||
|
|
64a4f19539 | ||
|
|
c35661e16e | ||
|
|
69b6179521 | ||
|
|
3d2e167e78 | ||
|
|
aa1955221c | ||
|
|
7a9b11e2c4 | ||
|
|
a82c0d0e50 | ||
|
|
35a6daa10d | ||
|
|
bf5db4e44a | ||
|
|
a87e8aa82b | ||
|
|
e00d87a791 | ||
|
|
b61babca73 | ||
|
|
e403a0492e | ||
|
|
54a76e1401 | ||
|
|
91f6e36420 | ||
|
|
9e74fe558f | ||
|
|
ac8bcfac23 | ||
|
|
91b0a691ed | ||
|
|
3ced6ec2a0 | ||
|
|
650fe159ee | ||
|
|
b92657fb39 | ||
|
|
17dbe066c1 | ||
|
|
5acc88ee9a | ||
|
|
d4983af3fc | ||
|
|
6f2dcfd44e | ||
|
|
3df6435353 | ||
|
|
3ef03364be | ||
|
|
5396af9bfc | ||
|
|
56eddf3a93 | ||
|
|
6dd900e6b9 | ||
|
|
441a26b79c | ||
|
|
471d5ca17f | ||
|
|
536f995ab4 | ||
|
|
bb3c97828e | ||
|
|
c4d32d5418 | ||
|
|
8522fd9f27 | ||
|
|
ee93118688 | ||
|
|
47f9b89175 | ||
|
|
891fc55e25 | ||
|
|
988755f202 | ||
|
|
b5483a46f6 | ||
|
|
04fd2d6e82 | ||
|
|
4cdc7cf3c4 | ||
|
|
fbaa1028e6 | ||
|
|
df26e19c16 | ||
|
|
98501cf4c0 | ||
|
|
d0ac142871 | ||
|
|
d60c66ebd6 | ||
|
|
a515bdae56 | ||
|
|
cc40853903 | ||
|
|
121d69faf9 | ||
|
|
0a2d7c858a | ||
|
|
0962e1e563 | ||
|
|
a324768b3f | ||
|
|
0973ee512e | ||
|
|
f74e492448 | ||
|
|
e567627809 | ||
|
|
56965bc0ed | ||
|
|
e43d6520c5 | ||
|
|
1123909960 | ||
|
|
bc1853c2e7 | ||
|
|
318b734f96 | ||
|
|
2734b1d54a | ||
|
|
82510c1574 | ||
|
|
0a2b02f206 | ||
|
|
41ee127de8 | ||
|
|
9ddec59ddd | ||
|
|
b72b22c628 | ||
|
|
a685f30245 | ||
|
|
173ec6f0f8 | ||
|
|
349ab5343e | ||
|
|
cf97adab7c | ||
|
|
b6fc940f18 | ||
|
|
2fb685c0fe | ||
|
|
54be5847f7 | ||
|
|
bf4f37b514 | ||
|
|
9e577e7214 | ||
|
|
69185c06c2 | ||
|
|
e561016d07 | ||
|
|
9db4dadce3 | ||
|
|
fa40d511c2 | ||
|
|
ebfb86866f | ||
|
|
be853a0657 | ||
|
|
c880342099 | ||
|
|
d09bbd2171 | ||
|
|
c1823f719a | ||
|
|
39686e3f05 | ||
|
|
482973559d | ||
|
|
600dbd72f4 | ||
|
|
785337dc5d | ||
|
|
14a35f35c3 | ||
|
|
ed2e176285 | ||
|
|
2d79280999 | ||
|
|
8b5084ab43 | ||
|
|
b19356597b | ||
|
|
0bd412edbd | ||
|
|
1a6ba25e5d | ||
|
|
26d0cd18a1 | ||
|
|
31653fe76e | ||
|
|
a6d52223d5 | ||
|
|
1bf7c4084c | ||
|
|
419a3eef53 | ||
|
|
f62531bafd | ||
|
|
dc815dad14 | ||
|
|
bde2ce9b53 | ||
|
|
041af0100c | ||
|
|
2e18c5b8cf | ||
|
|
53f0c526f7 | ||
|
|
dc5c4eced0 | ||
|
|
0d8b6d03ed | ||
|
|
cfa364280f | ||
|
|
7a293563fb | ||
|
|
9359954233 | ||
|
|
9fcc05676e | ||
|
|
1e95f5de49 | ||
|
|
f8983e9e5c | ||
|
|
aab71f03b3 | ||
|
|
447d88bf82 | ||
|
|
897e94f2f4 | ||
|
|
87936e5b52 | ||
|
|
5e02762715 | ||
|
|
0785820539 | ||
|
|
bb6d545aae | ||
|
|
4cdc94d92f | ||
|
|
c28dca6fc0 | ||
|
|
95e630201a | ||
|
|
84100be7eb | ||
|
|
0c7007ae9a | ||
|
|
3e7e3669fe | ||
|
|
1d3bb9c287 | ||
|
|
cacc624f5a | ||
|
|
04dd824f0a | ||
|
|
dc929732b1 | ||
|
|
a47fd23199 | ||
|
|
d205da0aa4 | ||
|
|
c414f12527 | ||
|
|
d9418c9fe3 | ||
|
|
65e2ba9bd0 | ||
|
|
b3f808644f | ||
|
|
1e30435525 | ||
|
|
156d25741b | ||
|
|
de3ee812cd | ||
|
|
234fb33864 | ||
|
|
c168bb5201 | ||
|
|
0ce5d774cb | ||
|
|
d2c2bbd711 | ||
|
|
88193adebb | ||
|
|
eac44f9496 | ||
|
|
6400095f1f | ||
|
|
b57267e60a | ||
|
|
79541b6ba7 | ||
|
|
0420098e94 | ||
|
|
9f80634be4 | ||
|
|
25ecb27aed | ||
|
|
2ff2efd4b2 | ||
|
|
ff7a29179d | ||
|
|
8403f8ac2a | ||
|
|
df0d4fa726 | ||
|
|
2a4bb5a11d | ||
|
|
2b6c5e42b5 | ||
|
|
a1a4765a94 | ||
|
|
64d0bc7a16 | ||
|
|
b1fb43baf5 | ||
|
|
bb309ca843 | ||
|
|
760662263f | ||
|
|
bce5205cf1 | ||
|
|
0b21726af6 | ||
|
|
04a7d256c5 | ||
|
|
8a9915b58a | ||
|
|
820e867804 | ||
|
|
699314a781 | ||
|
|
86552e62ff | ||
|
|
13769d3cdc | ||
|
|
158ae0de30 | ||
|
|
04056513d7 | ||
|
|
2b7e6dda2f | ||
|
|
b11464542a | ||
|
|
b97568f394 | ||
|
|
18e8839b8c | ||
|
|
f5e5bfae81 | ||
|
|
b27ab75e07 | ||
|
|
5799e4015f | ||
|
|
2f72ed78e1 | ||
|
|
5f3f038a6b | ||
|
|
5b29852c0a | ||
|
|
9616e43035 | ||
|
|
b918425e72 | ||
|
|
9ed5f5b6fc | ||
|
|
1721d118a8 | ||
|
|
8dc400c65a | ||
|
|
a5ac793443 | ||
|
|
8d6d995d78 | ||
|
|
4652255d4f | ||
|
|
a274f2e5ca | ||
|
|
ed4528096a | ||
|
|
f7946af404 | ||
|
|
26ead270a2 | ||
|
|
c45c784c58 | ||
|
|
6b6977cc00 | ||
|
|
41a6abd2e4 | ||
|
|
d59ccc0f34 | ||
|
|
870946d01b | ||
|
|
3bf68ef9d4 | ||
|
|
60d7d51a0a | ||
|
|
61deb2c873 | ||
|
|
0ae27d4212 | ||
|
|
3c96622313 | ||
|
|
8066ce6f49 | ||
|
|
b7f9ffc51a | ||
|
|
4a873a5ae3 | ||
|
|
c080deb0b8 | ||
|
|
8cefa8181c | ||
|
|
a34dd651b1 | ||
|
|
a86041836f | ||
|
|
6dbd760a2e | ||
|
|
4181a84e9b | ||
|
|
b4d5168409 | ||
|
|
74756c0703 | ||
|
|
dce57d6fdd | ||
|
|
d55df67642 | ||
|
|
769d9063d5 | ||
|
|
8f95a23df9 | ||
|
|
9816c33015 | ||
|
|
1d14c976f2 | ||
|
|
63c436e0ac | ||
|
|
a4700d7a9d | ||
|
|
e5a1fe0771 | ||
|
|
b101ff86a9 | ||
|
|
0c5a6044a0 | ||
|
|
6fc7c0b9ad | ||
|
|
9459e82161 | ||
|
|
62501eb3b8 | ||
|
|
df000b7508 | ||
|
|
c04bde6725 | ||
|
|
1fe8ae39cb | ||
|
|
e2049b9564 | ||
|
|
49d9836ab4 | ||
|
|
4ed38575bf | ||
|
|
ef166cd70d | ||
|
|
d45665f092 | ||
|
|
10bae6dab7 | ||
|
|
ad25c86fdd | ||
|
|
f064ff35f3 | ||
|
|
55b44b41bf | ||
|
|
1f3e9b22f4 |
@@ -10,3 +10,9 @@ cdaa0acd61d3001407609915bd573b78565d5571
|
||||
|
||||
# prettier write again
|
||||
dfac7395fed95fc5d8ebca21b786ce70821942bb
|
||||
|
||||
# lint and format plugin-cloud
|
||||
fb7d1be2f3325d076b7c967b1730afcef37922c2
|
||||
|
||||
# lint and format create-payload-app
|
||||
5fd3d430001efe86515262ded5e26f00c1451181
|
||||
|
||||
5
.github/ISSUE_TEMPLATE/1.bug_report.yml
vendored
5
.github/ISSUE_TEMPLATE/1.bug_report.yml
vendored
@@ -31,6 +31,11 @@ body:
|
||||
description: What version of Payload are you running?
|
||||
validations:
|
||||
required: true
|
||||
- type: input
|
||||
id: adapters-plugins
|
||||
attributes:
|
||||
label: Adapters and Plugins
|
||||
description: What adapters and plugins are you using? ie. db-mongodb, db-postgres, bundler-webpack, etc.
|
||||
- type: markdown
|
||||
attributes:
|
||||
value: Before submitting the issue, go through the steps you've written down to make sure the steps provided are detailed and clear.
|
||||
|
||||
2
.github/PULL_REQUEST_TEMPLATE.md
vendored
2
.github/PULL_REQUEST_TEMPLATE.md
vendored
@@ -2,7 +2,7 @@
|
||||
|
||||
<!-- Please include a summary of the pull request and any related issues it fixes. Please also include relevant motivation and context. -->
|
||||
|
||||
- [ ] I have read and understand the [CONTRIBUTING.md](../CONTRIBUTING.md) document in this repository.
|
||||
- [ ] I have read and understand the [CONTRIBUTING.md](https://github.com/payloadcms/payload/blob/main/CONTRIBUTING.md) document in this repository.
|
||||
|
||||
## Type of change
|
||||
|
||||
|
||||
174
.github/workflows/main.yml
vendored
174
.github/workflows/main.yml
vendored
@@ -108,6 +108,10 @@ jobs:
|
||||
tests-e2e:
|
||||
runs-on: ubuntu-latest
|
||||
needs: core-build
|
||||
strategy:
|
||||
fail-fast: false
|
||||
matrix:
|
||||
part: [1/4, 2/4, 3/4, 4/4]
|
||||
|
||||
steps:
|
||||
- name: Use Node.js 18
|
||||
@@ -128,14 +132,14 @@ jobs:
|
||||
key: ${{ github.sha }}-${{ github.run_number }}
|
||||
|
||||
- name: E2E Tests
|
||||
run: pnpm test:e2e --bail
|
||||
run: pnpm test:e2e --part ${{ matrix.part }} --bail
|
||||
|
||||
- uses: actions/upload-artifact@v3
|
||||
if: always()
|
||||
with:
|
||||
name: test-results
|
||||
path: test-results/
|
||||
retention-days: 30
|
||||
retention-days: 1
|
||||
|
||||
tests-type-generation:
|
||||
runs-on: ubuntu-latest
|
||||
@@ -165,11 +169,21 @@ jobs:
|
||||
- name: Generate GraphQL schema file
|
||||
run: pnpm dev:generate-graphql-schema graphql-schema-gen
|
||||
|
||||
# DB Adapters
|
||||
|
||||
build-db-mongodb:
|
||||
build-packages:
|
||||
runs-on: ubuntu-latest
|
||||
needs: core-build
|
||||
strategy:
|
||||
fail-fast: false
|
||||
matrix:
|
||||
pkg:
|
||||
- db-mongodb
|
||||
- db-postgres
|
||||
- bundler-webpack
|
||||
- bundler-vite
|
||||
- richtext-slate
|
||||
- richtext-lexical
|
||||
- live-preview
|
||||
- live-preview-react
|
||||
|
||||
steps:
|
||||
- name: Use Node.js 18
|
||||
@@ -189,12 +203,18 @@ jobs:
|
||||
path: ./*
|
||||
key: ${{ github.sha }}-${{ github.run_number }}
|
||||
|
||||
- name: Build db-mongodb
|
||||
run: pnpm turbo run build --filter=db-mongodb
|
||||
- name: Build ${{ matrix.pkg }}
|
||||
run: pnpm turbo run build --filter=${{ matrix.pkg }}
|
||||
|
||||
build-db-postgres:
|
||||
plugins:
|
||||
runs-on: ubuntu-latest
|
||||
needs: core-build
|
||||
strategy:
|
||||
fail-fast: false
|
||||
matrix:
|
||||
pkg:
|
||||
- plugin-cloud
|
||||
- create-payload-app
|
||||
|
||||
steps:
|
||||
- name: Use Node.js 18
|
||||
@@ -214,137 +234,9 @@ jobs:
|
||||
path: ./*
|
||||
key: ${{ github.sha }}-${{ github.run_number }}
|
||||
|
||||
- name: Build db-postgres
|
||||
run: pnpm turbo run build --filter=db-postgres
|
||||
- name: Build ${{ matrix.pkg }}
|
||||
run: pnpm turbo run build --filter=${{ matrix.pkg }}
|
||||
|
||||
# Bundlers
|
||||
|
||||
build-bundler-webpack:
|
||||
runs-on: ubuntu-latest
|
||||
needs: core-build
|
||||
|
||||
steps:
|
||||
- name: Use Node.js 18
|
||||
uses: actions/setup-node@v3
|
||||
with:
|
||||
node-version: 18
|
||||
|
||||
- name: Install pnpm
|
||||
uses: pnpm/action-setup@v2
|
||||
with:
|
||||
version: 8
|
||||
run_install: false
|
||||
|
||||
- name: Restore build
|
||||
uses: actions/cache@v3
|
||||
with:
|
||||
path: ./*
|
||||
key: ${{ github.sha }}-${{ github.run_number }}
|
||||
|
||||
- name: Build bundler-webpack
|
||||
run: pnpm turbo run build --filter=bundler-webpack
|
||||
|
||||
build-bundler-vite:
|
||||
runs-on: ubuntu-latest
|
||||
needs: core-build
|
||||
|
||||
steps:
|
||||
- name: Use Node.js 18
|
||||
uses: actions/setup-node@v3
|
||||
with:
|
||||
node-version: 18
|
||||
|
||||
- name: Install pnpm
|
||||
uses: pnpm/action-setup@v2
|
||||
with:
|
||||
version: 8
|
||||
run_install: false
|
||||
|
||||
- name: Restore build
|
||||
uses: actions/cache@v3
|
||||
with:
|
||||
path: ./*
|
||||
key: ${{ github.sha }}-${{ github.run_number }}
|
||||
|
||||
- name: Build bundler-vite
|
||||
run: pnpm turbo run build --filter=bundler-vite
|
||||
|
||||
# Other Plugins
|
||||
|
||||
build-plugin-richtext-slate:
|
||||
runs-on: ubuntu-latest
|
||||
needs: core-build
|
||||
|
||||
steps:
|
||||
- name: Use Node.js 18
|
||||
uses: actions/setup-node@v3
|
||||
with:
|
||||
node-version: 18
|
||||
|
||||
- name: Install pnpm
|
||||
uses: pnpm/action-setup@v2
|
||||
with:
|
||||
version: 8
|
||||
run_install: false
|
||||
|
||||
- name: Restore build
|
||||
uses: actions/cache@v3
|
||||
with:
|
||||
path: ./*
|
||||
key: ${{ github.sha }}-${{ github.run_number }}
|
||||
|
||||
- name: Build richtext-slate
|
||||
run: pnpm turbo run build --filter=richtext-slate
|
||||
|
||||
build-plugin-richtext-lexical:
|
||||
runs-on: ubuntu-latest
|
||||
needs: core-build
|
||||
|
||||
steps:
|
||||
- name: Use Node.js 18
|
||||
uses: actions/setup-node@v3
|
||||
with:
|
||||
node-version: 18
|
||||
|
||||
- name: Install pnpm
|
||||
uses: pnpm/action-setup@v2
|
||||
with:
|
||||
version: 8
|
||||
run_install: false
|
||||
|
||||
- name: Restore build
|
||||
uses: actions/cache@v3
|
||||
with:
|
||||
path: ./*
|
||||
key: ${{ github.sha }}-${{ github.run_number }}
|
||||
|
||||
- name: Build richtext-lexical
|
||||
run: pnpm turbo run build --filter=richtext-lexical
|
||||
|
||||
build-plugin-live-preview:
|
||||
runs-on: ubuntu-latest
|
||||
needs: core-build
|
||||
|
||||
steps:
|
||||
- name: Use Node.js 18
|
||||
uses: actions/setup-node@v3
|
||||
with:
|
||||
node-version: 18
|
||||
|
||||
- name: Install pnpm
|
||||
uses: pnpm/action-setup@v2
|
||||
with:
|
||||
version: 8
|
||||
run_install: false
|
||||
|
||||
- name: Restore build
|
||||
uses: actions/cache@v3
|
||||
with:
|
||||
path: ./*
|
||||
key: ${{ github.sha }}-${{ github.run_number }}
|
||||
|
||||
- name: Build live-preview
|
||||
run: pnpm turbo run build --filter=live-preview
|
||||
|
||||
- name: Build live-preview-react
|
||||
run: pnpm turbo run build --filter=live-preview-react
|
||||
- name: Test ${{ matrix.pkg }}
|
||||
run: pnpm --filter ${{ matrix.pkg }} run test
|
||||
if: matrix.pkg != 'create-payload-app' # degit doesn't work within GitHub Actions
|
||||
|
||||
2
.vscode/launch.json
vendored
2
.vscode/launch.json
vendored
@@ -17,7 +17,7 @@
|
||||
"type": "node-terminal"
|
||||
},
|
||||
{
|
||||
"command": "pnpm run dev:postgres collections-graphql",
|
||||
"command": "pnpm run dev:postgres fields",
|
||||
"cwd": "${workspaceFolder}",
|
||||
"name": "Run Dev Postgres",
|
||||
"request": "launch",
|
||||
|
||||
@@ -121,6 +121,12 @@ This means that in some fringe cases, if you are creating a doc and then instant
|
||||
|
||||
To avoid any issues, you can pass the `req.transactionID` through to your Local API calls, so that your Local API calls are included as part of the parent transaction.
|
||||
|
||||
### ⚠️ Locales now have more functionality, and in some places, you might need to update custom code
|
||||
|
||||
Payload's locales have become more powerful and now allow you to customize more aspects per locale such as a human-friendly label and if the locale is RTL or not.
|
||||
|
||||
This means that certain functions now return a different shape, such as `useLocale`. This hook used to return a string of the locale code you are currently editing, but it now returns an object with type of `Locale`.
|
||||
|
||||
### ⚠️ Admin panel CSS classes may have changed
|
||||
|
||||
The revisions we've made in 2.0 required changes to both HTML and CSS within the admin panel. For this reason, if you were loading custom CSS into the admin panel to customize the look and feel, your stylesheets may need to be updated. If your CSS is targeting elements on the page using HTML selectors or class names, you may need to update these selectors based on the current markup. It may also be necessary to update your style definitions if the core Payload component you are targeting has undergone significant change.
|
||||
|
||||
@@ -26,7 +26,8 @@
|
||||
</h4>
|
||||
<hr/>
|
||||
|
||||
### 🎉 Payload 2.0 is now available! Read more in the [announcement post](https://payloadcms.com/blog/payload-2-0).
|
||||
> [!IMPORTANT]
|
||||
> 🎉 <strong>Payload 2.0 is now available!<strong> Read more in the <a target="_blank" href="https://payloadcms.com/blog/payload-2-0" rel="dofollow"><strong>announcement post</strong></a>.
|
||||
|
||||
<h3>Benefits over a regular CMS</h3>
|
||||
<ul>
|
||||
|
||||
@@ -12,13 +12,13 @@ Payload has two official bundlers, the [Webpack Bundler](/docs/admin/webpack) an
|
||||
Webpack (recommended):
|
||||
|
||||
```text
|
||||
yarn add @payloadcms/webpack-bundler
|
||||
yarn add @payloadcms/bundler-webpack
|
||||
```
|
||||
|
||||
Vite (beta):
|
||||
|
||||
```text
|
||||
yarn add @payloadcms/vite-bundler
|
||||
yarn add @payloadcms/bundler-vite
|
||||
```
|
||||
|
||||
##### Configure the bundler
|
||||
|
||||
@@ -129,7 +129,7 @@ To add a _new_ view to the Admin Panel, simply add another key to the `views` ob
|
||||
}
|
||||
```
|
||||
|
||||
_For more examples regarding how to customize components, look at the following [examples](https://github.com/payloadcms/payload/tree/master/test/admin/components)._
|
||||
_For more examples regarding how to customize components, look at the following [examples](https://github.com/payloadcms/payload/tree/main/test/admin/components)._
|
||||
|
||||
For help on how to build your own custom view components, see [building a custom view component](#building-a-custom-view-component).
|
||||
|
||||
@@ -399,12 +399,12 @@ Your custom view components will be given all the props that a React Router `<Ro
|
||||
|
||||
#### Example
|
||||
|
||||
You can find examples of custom views in the [Payload source code `/test/admin/components/views` folder](https://github.com/payloadcms/payload/tree/master/test/admin/components/views). There, you'll find two custom views:
|
||||
You can find examples of custom views in the [Payload source code `/test/admin/components/views` folder](https://github.com/payloadcms/payload/tree/main/test/admin/components/views). There, you'll find two custom views:
|
||||
|
||||
1. A custom view that uses the `DefaultTemplate`, which is the built-in Payload template that displays the sidebar and "eyebrow nav"
|
||||
1. A custom view that uses the `MinimalTemplate` - which is just a centered template used for things like logging in or out
|
||||
|
||||
To see how to pass in your custom views to create custom views of your own, take a look at the `admin.components.views` property of the [Payload test admin config](https://github.com/payloadcms/payload/blob/master/test/admin/config.ts).
|
||||
To see how to pass in your custom views to create custom views of your own, take a look at the `admin.components.views` property of the [Payload test admin config](https://github.com/payloadcms/payload/blob/main/test/admin/config.ts).
|
||||
|
||||
### Fields
|
||||
|
||||
|
||||
@@ -347,7 +347,7 @@ The `useForm` hook returns an object with the following properties: |
|
||||
value: <strong><code>rowIndex</code></strong>,
|
||||
},
|
||||
{
|
||||
value: "The index of the row to add",
|
||||
value: "The index of the row to add. If omitted, the row will be added to the end of the array.",
|
||||
},
|
||||
],
|
||||
[
|
||||
|
||||
@@ -28,25 +28,25 @@ When bundled, it is code-split, highly performant (even with 100+ fields), and w
|
||||
|
||||
All options for the Admin panel are defined in your base Payload config file.
|
||||
|
||||
| Option | Description |
|
||||
| --------------------- | ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------ |
|
||||
| `user` | The `slug` of a Collection that you want be used to log in to the Admin dashboard. [More](/docs/admin/overview#the-admin-user-collection) |
|
||||
| `buildPath` | Specify an absolute path for where to store the built Admin panel bundle used in production. Defaults to `path.resolve(process.cwd(), 'build')`. |
|
||||
| `meta` | Base meta data to use for the Admin panel. Included properties are `titleSuffix`, `ogImage`, and `favicon`. |
|
||||
| `disable` | If set to `true`, the entire Admin panel will be disabled. |
|
||||
| `indexHTML` | Optionally replace the entirety of the `index.html` file used by the Admin panel. Reference the [base index.html file](https://github.com/payloadcms/payload/blob/main/packages/payload/src/admin/index.html) to ensure your replacement has the appropriate HTML elements. |
|
||||
| `css` | Absolute path to a stylesheet that you can use to override / customize the Admin panel styling. [More](/docs/admin/customizing-css). |
|
||||
| `scss` | Absolute path to a Sass variables / mixins stylesheet meant to override Payload styles to make for an easy re-skinning of the Admin panel. [More](/docs/admin/customizing-css#overriding-scss-variables). |
|
||||
| `dateFormat` | Global date format that will be used for all dates in the Admin panel. Any valid [date-fns](https://date-fns.org/) format pattern can be used. |
|
||||
| `avatar` | Set account profile picture. Options: `gravatar`, `default` or a custom React component. |
|
||||
| `autoLogin` | Used to automate admin log-in for dev and demonstration convenience. [More](/docs/authentication/config). |
|
||||
| `livePreview` | Enable real-time editing for instant visual feedback of your front-end application. [More](/docs/live-preview/overview). |
|
||||
| `components` | Component overrides that affect the entirety of the Admin panel. [More](/docs/admin/components) |
|
||||
| `webpack` | Customize the Webpack config that's used to generate the Admin panel. [More](/docs/admin/webpack) |
|
||||
| `vite` | Customize the Vite config that's used to generate the Admin panel. [More](/docs/admin/vite) |
|
||||
| **`bundler`** | The bundler that you would like to use to bundle the admin panel. Officially supported bundlers: [Webpack](/docs/admin/webpack) and [Vite](/docs/admin/vite). |
|
||||
| **`logoutRoute`** | The route for the `logout` page. |
|
||||
| **`inactivityRoute`** | The route for the `logout` inactivity page. |
|
||||
| Option | Description |
|
||||
| ----------------- | --------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
|
||||
| `bundler` | The bundler that you would like to use to bundle the admin panel. Officially supported bundlers: [Webpack](/docs/admin/webpack) and [Vite](/docs/admin/vite). |
|
||||
| `user` | The `slug` of a Collection that you want be used to log in to the Admin dashboard. [More](/docs/admin/overview#the-admin-user-collection) |
|
||||
| `buildPath` | Specify an absolute path for where to store the built Admin panel bundle used in production. Defaults to `path.resolve(process.cwd(), 'build')`. |
|
||||
| `meta` | Base meta data to use for the Admin panel. Included properties are `titleSuffix`, `ogImage`, and `favicon`. |
|
||||
| `disable` | If set to `true`, the entire Admin panel will be disabled. |
|
||||
| `indexHTML` | Optionally replace the entirety of the `index.html` file used by the Admin panel. Reference the [base index.html file](https://github.com/payloadcms/payload/blob/main/packages/payload/src/admin/index.html) to ensure your replacement has the appropriate HTML elements. |
|
||||
| `css` | Absolute path to a stylesheet that you can use to override / customize the Admin panel styling. [More](/docs/admin/customizing-css). |
|
||||
| `scss` | Absolute path to a Sass variables / mixins stylesheet meant to override Payload styles to make for an easy re-skinning of the Admin panel. [More](/docs/admin/customizing-css#overriding-scss-variables). |
|
||||
| `dateFormat` | Global date format that will be used for all dates in the Admin panel. Any valid [date-fns](https://date-fns.org/) format pattern can be used. |
|
||||
| `avatar` | Set account profile picture. Options: `gravatar`, `default` or a custom React component. |
|
||||
| `autoLogin` | Used to automate admin log-in for dev and demonstration convenience. [More](/docs/authentication/config). |
|
||||
| `livePreview` | Enable real-time editing for instant visual feedback of your front-end application. [More](/docs/live-preview/overview). |
|
||||
| `components` | Component overrides that affect the entirety of the Admin panel. [More](/docs/admin/components) |
|
||||
| `webpack` | Customize the Webpack config that's used to generate the Admin panel. [More](/docs/admin/webpack) |
|
||||
| `vite` | Customize the Vite config that's used to generate the Admin panel. [More](/docs/admin/vite) |
|
||||
| `logoutRoute` | The route for the `logout` page. |
|
||||
| `inactivityRoute` | The route for the `logout` inactivity page. |
|
||||
|
||||
### The Admin User Collection
|
||||
|
||||
|
||||
@@ -29,7 +29,6 @@ It's often best practice to write your Collections in separate files and then im
|
||||
| **`graphQL`** | An object with `singularName` and `pluralName` strings used in schema generation. Auto-generated from slug if not defined. Set to `false` to disable GraphQL. |
|
||||
| **`typescript`** | An object with property `interface` as the text used in schema generation. Auto-generated from slug if not defined. |
|
||||
| **`defaultSort`** | Pass a top-level field to sort by default in the collection List view. Prefix the name of the field with a minus symbol ("-") to sort in descending order. |
|
||||
| **`pagination`** | Set pagination-specific options for this collection. [More](#pagination) |
|
||||
| **`custom`** | Extension point for adding custom data (e.g. for plugins) |
|
||||
|
||||
_\* An asterisk denotes that a property is required._
|
||||
@@ -84,6 +83,7 @@ property on a collection's config.
|
||||
| `livePreview` | Enable real-time editing for instant visual feedback of your front-end application. [More](/docs/live-preview/overview). |
|
||||
| `components` | Swap in your own React components to be used within this collection. [More](/docs/admin/components#collections) |
|
||||
| `listSearchableFields` | Specify which fields should be searched in the List search view. [More](#list-searchable-fields) |
|
||||
| **`pagination`** | Set pagination-specific options for this collection. [More](#pagination) |
|
||||
|
||||
### Preview
|
||||
|
||||
|
||||
@@ -65,7 +65,7 @@ After a user logs in, they can change their language selection in the `/account`
|
||||
<strong>Note:</strong>
|
||||
<br />
|
||||
If there is a language that Payload does not yet support, we accept code
|
||||
[contributions](https://github.com/payloadcms/payload/blob/main/contributing.md).
|
||||
[contributions](https://github.com/payloadcms/payload/blob/main/CONTRIBUTING.md).
|
||||
</Banner>
|
||||
|
||||
### Node Express
|
||||
|
||||
@@ -19,48 +19,53 @@ Payload is a _config-based_, code-first CMS and application framework. The Paylo
|
||||
|
||||
## Options
|
||||
|
||||
| Option | Description |
|
||||
| --------------------- | ----------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
|
||||
| `serverURL` | A string used to define the absolute URL of your app including the protocol, for example `https://example.com`. No paths allowed, only protocol, domain and (optionally) port |
|
||||
| `collections` | An array of all Collections that Payload will manage. To read more about how to define your collection configs, [click here](/docs/configuration/collections). |
|
||||
| `cors` | Either a whitelist array of URLS to allow CORS requests from, or a wildcard string (`'*'`) to accept incoming requests from any domain. |
|
||||
| `globals` | An array of all Globals that Payload will manage. For more on Globals and their configs, [click here](/docs/configuration/globals). |
|
||||
| `admin` | Base Payload admin configuration. Specify custom components, control metadata, set the Admin user collection, and [more](/docs/admin/overview#admin-options). |
|
||||
| `editor` | Default richText editor which will be used by richText fields. |
|
||||
| `localization` | Opt-in and control how Payload handles the translation of your content into multiple locales. [More](/docs/configuration/localization) |
|
||||
| `graphQL` | Manage GraphQL-specific functionality here. Define your own queries and mutations, manage query complexity limits, and [more](/docs/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 cookies to be accepted from as a form of CSRF protection. [More](/docs/authentication/overview#csrf-protection) |
|
||||
| `defaultDepth` | If a user does not specify `depth` while requesting a resource, this depth will be used. [More](/docs/getting-started/concepts#depth) |
|
||||
| `maxDepth` | The maximum allowed depth to be permitted application-wide. This setting helps prevent against malicious queries. Defaults to `10`. |
|
||||
| `indexSortableFields` | Automatically index all sortable top-level fields in the database to improve sort performance and add database compatibility for Azure Cosmos and similar. |
|
||||
| `upload` | Base Payload upload configuration. [More](/docs/upload/overview#payload-wide-upload-options). |
|
||||
| `routes` | Control the routing structure that Payload binds itself to. Specify `admin`, `api`, `graphQL`, and `graphQLPlayground`. |
|
||||
| `email` | Base email settings to allow Payload to generate email such as Forgot Password requests and other requirements. [More](/docs/email/overview#configuration) |
|
||||
| `express` | Express-specific middleware options such as compression and JSON parsing. [More](/docs/configuration/express) |
|
||||
| `debug` | Enable to expose more detailed error information. |
|
||||
| `telemetry` | Disable Payload telemetry by passing `false`. [More](/docs/configuration/overview#telemetry) |
|
||||
| `rateLimit` | Control IP-based rate limiting for all Payload resources. Used to prevent DDoS attacks and [more](/docs/production/preventing-abuse#rate-limiting-requests). |
|
||||
| `hooks` | Tap into Payload-wide hooks. [More](/docs/hooks/overview) |
|
||||
| `plugins` | An array of Payload plugins. [More](/docs/plugins/overview) |
|
||||
| `endpoints` | An array of custom API endpoints added to the Payload router. [More](/docs/rest-api/overview#custom-endpoints) |
|
||||
| `custom` | Extension point for adding custom data (e.g. for plugins) |
|
||||
| Option | Description |
|
||||
| --------------------- | --------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
|
||||
| `admin` \* | Base Payload admin configuration. Specify bundler*, custom components, control metadata, set the Admin user collection, and [more](/docs/admin/overview#admin-options). Required. |
|
||||
| `editor` \* | Rich Text Editor which will be used by richText fields. Required. |
|
||||
| `db` \* | Database Adapter which will be used by Payload. Read more [here](/docs/database/overview). Required. |
|
||||
| `serverURL` | A string used to define the absolute URL of your app including the protocol, for example `https://example.com`. No paths allowed, only protocol, domain and (optionally) port |
|
||||
| `collections` | An array of all Collections that Payload will manage. To read more about how to define your collection configs, [click here](/docs/configuration/collections). |
|
||||
| `globals` | An array of all Globals that Payload will manage. For more on Globals and their configs, [click here](/docs/configuration/globals). |
|
||||
| `cors` | Either a whitelist array of URLS to allow CORS requests from, or a wildcard string (`'*'`) to accept incoming requests from any domain. |
|
||||
| `localization` | Opt-in and control how Payload handles the translation of your content into multiple locales. [More](/docs/configuration/localization) |
|
||||
| `graphQL` | Manage GraphQL-specific functionality here. Define your own queries and mutations, manage query complexity limits, and [more](/docs/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 cookies to be accepted from as a form of CSRF protection. [More](/docs/authentication/overview#csrf-protection) |
|
||||
| `defaultDepth` | If a user does not specify `depth` while requesting a resource, this depth will be used. [More](/docs/getting-started/concepts#depth) |
|
||||
| `maxDepth` | The maximum allowed depth to be permitted application-wide. This setting helps prevent against malicious queries. Defaults to `10`. |
|
||||
| `indexSortableFields` | Automatically index all sortable top-level fields in the database to improve sort performance and add database compatibility for Azure Cosmos and similar. |
|
||||
| `upload` | Base Payload upload configuration. [More](/docs/upload/overview#payload-wide-upload-options). |
|
||||
| `routes` | Control the routing structure that Payload binds itself to. Specify `admin`, `api`, `graphQL`, and `graphQLPlayground`. |
|
||||
| `email` | Base email settings to allow Payload to generate email such as Forgot Password requests and other requirements. [More](/docs/email/overview#configuration) |
|
||||
| `express` | Express-specific middleware options such as compression and JSON parsing. [More](/docs/configuration/express) |
|
||||
| `debug` | Enable to expose more detailed error information. |
|
||||
| `telemetry` | Disable Payload telemetry by passing `false`. [More](/docs/configuration/overview#telemetry) |
|
||||
| `rateLimit` | Control IP-based rate limiting for all Payload resources. Used to prevent DDoS attacks and [more](/docs/production/preventing-abuse#rate-limiting-requests). |
|
||||
| `hooks` | Tap into Payload-wide hooks. [More](/docs/hooks/overview) |
|
||||
| `plugins` | An array of Payload plugins. [More](/docs/plugins/overview) |
|
||||
| `endpoints` | An array of custom API endpoints added to the Payload router. [More](/docs/rest-api/overview#custom-endpoints) |
|
||||
| `custom` | Extension point for adding custom data (e.g. for plugins) |
|
||||
|
||||
_\* An asterisk denotes that a property is required._
|
||||
|
||||
#### Simple example
|
||||
|
||||
```ts
|
||||
import { buildConfig } from 'payload/config'
|
||||
import { mongooseAdapter } from '@payloadcms/db-mongodb'
|
||||
import { postgresAdapter } from '@payloadcms/db-postgres'
|
||||
import { postgresAdapter } from '@payloadcms/db-postgres' // beta
|
||||
|
||||
import { viteBundler } from '@payloadcms/bundler-vite'
|
||||
import { webpackBundler } from '@payloadcms/bundler-webpack'
|
||||
|
||||
import { lexicalEditor } from '@payloadcms/richtext-lexical'
|
||||
import { lexicalEditor } from '@payloadcms/richtext-lexical' // beta
|
||||
import { slateEditor } from '@payloadcms/richtext-slate'
|
||||
|
||||
export default buildConfig({
|
||||
bundler: webpackBundler() // or viteBundler(),
|
||||
admin: {
|
||||
bundler: webpackBundler(), // or viteBundler()
|
||||
},
|
||||
db: mongooseAdapter({}) // or postgresAdapter({}),
|
||||
editor: lexicalEditor({}) // or slateEditor({})
|
||||
collections: [
|
||||
|
||||
@@ -8,7 +8,7 @@ keywords: headless cms, typescript, documentation, Content Management System, cm
|
||||
|
||||
When working with GraphQL it is useful to have the schema for development of other projects that need to call on your GraphQL endpoint. In Payload the schema is controlled by your collections and globals and is made available to the developer or third parties, it is not necessary for developers using Payload to write schema types manually.
|
||||
|
||||
### Schema generatation script
|
||||
### Schema generation script
|
||||
|
||||
Run the following command in a Payload project to generate your project's GraphQL schema from Payload:
|
||||
|
||||
|
||||
@@ -88,13 +88,14 @@ This package provides the following functions:
|
||||
| --------------------- | ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
|
||||
| **`subscribe`** | Subscribes to the Admin panel's `window.postMessage` events and calls the provided callback function. |
|
||||
| **`unsubscribe`** | Unsubscribes from the Admin panel's `window.postMessage` events. |
|
||||
| **`ready`** | Sends a `window.postMessage` event to the Admin panel to indicate that the front-end is ready to receive messages. |
|
||||
|
||||
The `subscribe` function takes the following args:
|
||||
|
||||
| Path | Description |
|
||||
| --------------------- | ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
|
||||
| **`callback`** \* | A callback function that is called with `data` every time a change is made to the document. |
|
||||
| **`serverURL`** \* | The URL of your Payload server. git s |
|
||||
| **`serverURL`** \* | The URL of your Payload server. |
|
||||
| **`initialData`** | The initial data of the document. The live data will be merged in as changes are made. |
|
||||
| **`depth`** | The depth of the relationships to fetch. Defaults to `0`. |
|
||||
|
||||
@@ -103,18 +104,23 @@ With these functions, you can build your own hook using your front-end framework
|
||||
```tsx
|
||||
import { subscribe, unsubscribe } from '@payloadcms/live-preview';
|
||||
|
||||
// Build your own hook to subscribe to the live preview events
|
||||
// This function will handle everything for you like
|
||||
// 1. subscribing to `window.postMessage` events
|
||||
// 2. merging initial page data with incoming form state
|
||||
// 3. populating relationships and uploads
|
||||
// To build your own hook, subscribe to Live Preview events using the`subscribe` function
|
||||
// It handles everything from:
|
||||
// 1. Listening to `window.postMessage` events
|
||||
// 2. Merging initial data with active form state
|
||||
// 3. Populating relationships and uploads
|
||||
// 4. Calling the `onChange` callback with the result
|
||||
// Your hook should also:
|
||||
// 1. Tell the Admin panel when it is ready to receive messages
|
||||
// 2. Handle the results of the `onChange` callback to update the UI
|
||||
// 3. Unsubscribe from the `window.postMessage` events when it unmounts
|
||||
```
|
||||
|
||||
Here is an example of what the same `useLivePreview` React hook from above looks like under the hood:
|
||||
|
||||
```tsx
|
||||
import { subscribe, unsubscribe } from '@payloadcms/live-preview'
|
||||
import { useCallback, useEffect, useState } from 'react'
|
||||
import { subscribe, unsubscribe, ready } from '@payloadcms/live-preview'
|
||||
import { useCallback, useEffect, useState, useRef } from 'react'
|
||||
|
||||
export const useLivePreview = <T extends any>(props: {
|
||||
depth?: number
|
||||
@@ -127,13 +133,18 @@ export const useLivePreview = <T extends any>(props: {
|
||||
const { depth = 0, initialData, serverURL } = props
|
||||
const [data, setData] = useState<T>(initialData)
|
||||
const [isLoading, setIsLoading] = useState<boolean>(true)
|
||||
const hasSentReadyMessage = useRef<boolean>(false)
|
||||
|
||||
const onChange = useCallback((mergedData) => {
|
||||
// When a change is made, the `onChange` callback will be called with the merged data
|
||||
// Set this merged data into state so that React will re-render the UI
|
||||
setData(mergedData)
|
||||
setIsLoading(false)
|
||||
}, [])
|
||||
|
||||
useEffect(() => {
|
||||
// Listen for `window.postMessage` events from the Admin panel
|
||||
// When a change is made, the `onChange` callback will be called with the merged data
|
||||
const subscription = subscribe({
|
||||
callback: onChange,
|
||||
depth,
|
||||
@@ -141,6 +152,17 @@ export const useLivePreview = <T extends any>(props: {
|
||||
serverURL,
|
||||
})
|
||||
|
||||
// Once subscribed, send a `ready` message back up to the Admin panel
|
||||
// This will indicate that the front-end is ready to receive messages
|
||||
if (!hasSentReadyMessage.current) {
|
||||
hasSentReadyMessage.current = true
|
||||
|
||||
ready({
|
||||
serverURL
|
||||
})
|
||||
}
|
||||
|
||||
// When the component unmounts, unsubscribe from the `window.postMessage` events
|
||||
return () => {
|
||||
unsubscribe(subscription)
|
||||
}
|
||||
|
||||
@@ -8,7 +8,7 @@ keywords: live preview, preview, live, iframe, iframe preview, visual editing, d
|
||||
|
||||
**With Live Preview you can render your front-end application directly within the Admin panel. As you type, your changes take effect in real-time. No need to save a draft or publish your changes.**
|
||||
|
||||
Live Preview works by rendering an iframe on the page that loads your front-end application. The Admin panel communicates with your app through `window.postMessage` events. These events are emitted every time a change is made to the document. Your app then listens for these events and re-renders itself with the data it receives.
|
||||
Live Preview works by rendering an iframe on the page that loads your front-end application. The Admin panel communicates with your app through [`window.postMessage`](https://developer.mozilla.org/en-US/docs/Web/API/Window/postMessage) events. These events are emitted every time a change is made to the document. Your app then listens for these events and re-renders itself with the data it receives.
|
||||
|
||||
{/* IMAGE OF LIVE PREVIEW HERE */}
|
||||
|
||||
@@ -84,8 +84,8 @@ Here is an example of using a function that returns a dynamic URL:
|
||||
documentInfo,
|
||||
locale
|
||||
}) => `${data.tenant.url}${ // Multi-tenant top-level domain
|
||||
documentInfo.slug === 'posts' ? `/posts/${data.slug}` : `/${data.slug}
|
||||
`}?locale=${locale}`, // Localization query param
|
||||
documentInfo.slug === 'posts' ? `/posts/${data.slug}` : `${data.slug !== 'home' : `/${data.slug}` : ''}`
|
||||
}${locale ? `?locale=${locale?.code}` : ''}`, // Localization query param
|
||||
collections: ['pages'],
|
||||
},
|
||||
}
|
||||
|
||||
@@ -131,6 +131,7 @@ const result = await payload.find({
|
||||
depth: 2,
|
||||
page: 1,
|
||||
limit: 10,
|
||||
pagination: false, // If you want to disable pagination count, etc.
|
||||
where: {}, // pass a `where` query here
|
||||
sort: '-title',
|
||||
locale: 'en',
|
||||
@@ -412,7 +413,7 @@ dotenv.config({
|
||||
path: path.resolve(__dirname, '../.env'),
|
||||
})
|
||||
|
||||
const { PAYLOAD_SECRET, MONGODB_URI } = process.env
|
||||
const { PAYLOAD_SECRET } = process.env
|
||||
|
||||
const doAction = async (): Promise<void> => {
|
||||
await payload.init({
|
||||
|
||||
@@ -1,10 +1,10 @@
|
||||
---
|
||||
title: Admin panel compatibility
|
||||
label: Admin compatibility
|
||||
order: 20
|
||||
order: 30
|
||||
desc: NEEDS TO BE WRITTEN
|
||||
---
|
||||
|
||||
TODO: talk about how plugins need to ensure compatibility with both Vite and Webpack
|
||||
|
||||
- It's best practice to alias your plugin to an admin-only version, so if you have admin-only changes, put them in the admin plugin, and then leave the full plugin, complete with server code, to be installed on the server side
|
||||
<Banner type="success">
|
||||
COMING SOON: This page is a work in progress. Check back soon for more information.
|
||||
</Banner>
|
||||
|
||||
294
docs/plugins/build-your-own.mdx
Normal file
294
docs/plugins/build-your-own.mdx
Normal file
@@ -0,0 +1,294 @@
|
||||
---
|
||||
title: Building Your Own Plugin
|
||||
label: Build Your Own
|
||||
order: 20
|
||||
desc: Starting to build your own plugin? Find everything you need and learn best practices with the Payload plugin template.
|
||||
keywords: plugins, template, config, configuration, extensions, custom, documentation, Content Management System, cms, headless, javascript, node, react, express
|
||||
---
|
||||
|
||||
Building your own plugin is easy, and if you're already familiar with Payload then you'll have everything you need to get started. You can either start from scratch or use the Payload plugin template to get up and running quickly.
|
||||
|
||||
<Banner type="success">
|
||||
To use the template, run `npx create-payload-app@latest -t plugin -n my-new-plugin` directly in your terminal or [clone the template directly from GitHub](https://github.com/payloadcms/payload-plugin-template).
|
||||
</Banner>
|
||||
|
||||
Our plugin template includes everything you need to build a full life-cycle plugin:
|
||||
|
||||
* Example files and functions for extending the payload config
|
||||
* A local dev environment to develop the plugin
|
||||
* Test suite with integrated GitHub workflow
|
||||
|
||||
By abstracting your code into a plugin, you'll be able to reuse your feature across multiple projects and make it available for other developers to use.
|
||||
|
||||
### Plugins Recap
|
||||
|
||||
Here is a brief recap of how to integrate plugins with Payload, to learn more head back to the [plugin overview page](https://payloadcms.com/docs/plugins/overview).
|
||||
|
||||
|
||||
#### How to install a plugin
|
||||
|
||||
To install any plugin, simply add it to your Payload config in the plugins array.
|
||||
|
||||
```
|
||||
import samplePlugin from 'sample-plugin';
|
||||
|
||||
const config = buildConfig({
|
||||
plugins: [
|
||||
// Add plugins here
|
||||
samplePlugin({
|
||||
enabled: true,
|
||||
}),
|
||||
],
|
||||
});
|
||||
|
||||
export default config;
|
||||
```
|
||||
|
||||
|
||||
#### Initialization
|
||||
|
||||
The initialization process goes in the following order:
|
||||
|
||||
1. Incoming config is validated
|
||||
2. Plugins execute
|
||||
3. Default options are integrated
|
||||
4. Sanitization cleans and validates data
|
||||
5. Final config gets initialized
|
||||
|
||||
|
||||
### Plugin Template
|
||||
|
||||
In the [Payload plugin template](https://github.com/payloadcms/payload-plugin-template), you will see a common file structure that is used across plugins:
|
||||
|
||||
1. root folder - general configuration
|
||||
2. /src folder - everything related to the plugin
|
||||
3. /dev folder - sanitized test project for development
|
||||
|
||||
|
||||
#### Root
|
||||
|
||||
In the root folder, you will see various files related to the configuration of the plugin. We set up our environment in a similar manner in Payload core and across other projects. The only two files you need to modify are:
|
||||
|
||||
* **README**.md - This contains instructions on how to use the template. When you are ready, update this to contain instructions on how to use your Plugin.
|
||||
* **package**.json - Contains necessary scripts and dependencies. Overwrite the metadata in this file to describe your Plugin.
|
||||
|
||||
|
||||
#### Dev
|
||||
|
||||
The purpose of the **dev** folder is to provide a sanitized local Payload project. so you can run and test your plugin while you are actively developing it.
|
||||
|
||||
Do **not** store any of the plugin functionality in this folder - it is purely an environment to _assist_ you with developing the plugin.
|
||||
|
||||
If you're starting from scratch, you can easily setup a dev environment like this:
|
||||
|
||||
```
|
||||
mkdir dev
|
||||
cd dev
|
||||
npx create-payload-app
|
||||
```
|
||||
|
||||
If you're using the plugin template, the dev folder is built out for you and the `samplePlugin` has already been installed in `dev/payload.config()`.
|
||||
|
||||
```
|
||||
plugins: [
|
||||
// when you rename the plugin or add options, make sure to update it here
|
||||
samplePlugin({
|
||||
enabled: false,
|
||||
})
|
||||
]
|
||||
```
|
||||
|
||||
You can add to the `dev/payload.config` and build out the dev project as needed to test your plugin.
|
||||
|
||||
When you're ready to start development, navigate into this folder with `cd dev`
|
||||
|
||||
And then start the project with `yarn dev` and pull up `http://localhost:3000` in your browser.
|
||||
|
||||
|
||||
### Testing
|
||||
|
||||
Another benefit of the dev folder is that you have the perfect environment established for testing.
|
||||
|
||||
A good test suite is essential to ensure quality and stability in your plugin. Payload typically uses [Jest](https://jestjs.io/); a popular testing framework, widely used for testing JavaScript and particularly for applications built with React.
|
||||
|
||||
Jest organizes tests into test suites and cases. We recommend creating tests based on the expected behavior of your plugin from start to finish. Read more about tests in the [Jest documentation.](https://jestjs.io/)
|
||||
|
||||
The plugin template provides a stubbed out test suite at `dev/plugin.spec.ts` which is ready to go - just add in your own test conditions and you're all set!
|
||||
|
||||
```
|
||||
import payload from 'payload'
|
||||
|
||||
describe('Plugin tests', () => {
|
||||
// Example test to check for seeded data
|
||||
it('seeds data accordingly', async () => {
|
||||
const newCollectionQuery = await payload.find({
|
||||
collection: 'newCollection',
|
||||
sort: 'createdAt',
|
||||
})
|
||||
|
||||
newCollection = newCollectionQuery.docs
|
||||
|
||||
expect(newCollectionQuery.totalDocs).toEqual(1)
|
||||
})
|
||||
})
|
||||
```
|
||||
|
||||
|
||||
### Seeding data
|
||||
|
||||
For development and testing, you will likely need some data to work with. You can streamline this process by seeding and dropping your database - instead of manually entering data.
|
||||
|
||||
In the plugin template, you can navigate to `dev/src/server.ts` and see an example seed function.
|
||||
|
||||
```
|
||||
if (process.env.PAYLOAD_SEED === 'true') {
|
||||
await seed(payload)
|
||||
}
|
||||
```
|
||||
|
||||
A sample seed function has been created for you at `dev/src/seed`, update this file with additional data as needed.
|
||||
|
||||
```
|
||||
export const seed = async (payload: Payload): Promise<void> => {
|
||||
payload.logger.info('Seeding data...')
|
||||
|
||||
await payload.create({
|
||||
collection: 'new-collection',
|
||||
data: {
|
||||
title: 'Seeded title',
|
||||
},
|
||||
})
|
||||
|
||||
// Add additional seed data here
|
||||
}
|
||||
|
||||
```
|
||||
|
||||
|
||||
#### Src
|
||||
|
||||
Now that we have our environment setup and dev project ready to go - it's time to build the plugin!
|
||||
|
||||
|
||||
**index.ts**
|
||||
|
||||
First up, the `src/index.ts` file - this is where the plugin should be imported from. It is best practice not to build the plugin directly in this file, instead we use this to export the plugin and types from their respective files.
|
||||
|
||||
|
||||
**Plugin.ts**
|
||||
|
||||
To reiterate, the essence of a payload plugin is simply to extend the Payload config - and that is exactly what we are doing in this file.
|
||||
|
||||
```
|
||||
export const samplePlugin =
|
||||
(pluginOptions: PluginTypes) =>
|
||||
(incomingConfig: Config): Config => {
|
||||
let config = { ...incomingConfig }
|
||||
|
||||
// do something cool with the config here
|
||||
|
||||
return config
|
||||
}
|
||||
```
|
||||
|
||||
1. First, you need to receive the existing Payload config along with any plugin options.
|
||||
2. Then set the variable `config` to be equal to a copy of the existing config.
|
||||
3. From here, you can extend the config however you like!
|
||||
4. Finally, return the config and you're all set.
|
||||
|
||||
|
||||
### Spread Syntax
|
||||
|
||||
[Spread syntax](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Operators/Spread_syntax) (or the spread operator) is a feature in JavaScript that uses the dot notation **(...)** to spread elements from arrays, strings, or objects into various contexts.
|
||||
|
||||
We are going to use spread syntax to allow us to add data to existing arrays without losing the existing data. It is crucial to spread the existing data correctly, else this can cause adverse behavior and conflicts with Payload config and other plugins.
|
||||
|
||||
Let's say you want to build a plugin that adds a new collection:
|
||||
|
||||
```
|
||||
config.collections = [
|
||||
...(config.collections || []),
|
||||
newCollection,
|
||||
// Add additional collections here
|
||||
]
|
||||
```
|
||||
|
||||
First, you need to spread the `config.collections` to ensure that we don't lose the existing collections. Then you can add any additional collections, just as you would in a regular payload config.
|
||||
|
||||
This same logic is applied to other properties like admin, globals, hooks:
|
||||
|
||||
```
|
||||
config.globals = [
|
||||
...(config.globals || []),
|
||||
// Add additional globals here
|
||||
]
|
||||
|
||||
config.hooks = {
|
||||
...(config.hooks || {}),
|
||||
// Add additional hooks here
|
||||
}
|
||||
```
|
||||
|
||||
Some properties will be slightly different to extend, for instance the `onInit` property:
|
||||
|
||||
```
|
||||
config.onInit = async payload => {
|
||||
if (incomingConfig.onInit) await incomingConfig.onInit(payload)
|
||||
|
||||
// Add additional onInit code by using the onInitExtension function
|
||||
onInitExtension(pluginOptions, payload)
|
||||
}
|
||||
```
|
||||
|
||||
If you wish to add to the `onInit`, you must include the async/await. We don't use spread syntax in this case, instead you must await the existing `onInit` before running additional functionality.
|
||||
|
||||
In the template, we have stubbed out a basic `onInitExtension` file that you can use, if not needed feel free to delete it.
|
||||
|
||||
|
||||
### Webpack
|
||||
|
||||
If any of your files use server only packages such as fs, stripe, nodemailer, etc, they will need to be removed from the browser bundle. To do that, you can [alias the file imports with webpack](https://payloadcms.com/docs/admin/webpack#aliasing-server-only-modules).
|
||||
|
||||
When files are bundled for the browser, the import paths are essentially crawled to determine what files to include in the bundle. To prevent the server only files from making it into the bundle, we can alias their import paths to a file that can be included in the browser. This will short-circuit the import path crawling and ensure browser only code is bundled.
|
||||
|
||||
Webpack is another part of the Payload config that can be a little more tricky to extend. To help here, the template includes a helper function `extendWebpackConfig()` which takes care of spreading the existing webpack, so you can just add your new stuff:
|
||||
|
||||
```
|
||||
config.admin = {
|
||||
...(config.admin || {}),
|
||||
// Add your aliases to the helper function below
|
||||
webpack: extendWebpackConfig(incomingConfig)
|
||||
}
|
||||
```
|
||||
|
||||
### Types
|
||||
|
||||
If your plugin has options, you should define and provide types for these options in a separate file which gets exported from the main `index.ts`.
|
||||
|
||||
```
|
||||
export interface PluginTypes {
|
||||
/**
|
||||
* Enable or disable plugin
|
||||
* @default false
|
||||
*/
|
||||
enabled?: boolean
|
||||
}
|
||||
|
||||
```
|
||||
|
||||
If possible, include [JSDoc comments](https://www.typescriptlang.org/docs/handbook/jsdoc-supported-types.html#types-1) to describe the options and their types. This allows a developer to see details about the options in their editor.
|
||||
|
||||
### Best practices
|
||||
|
||||
In addition to the setup covered above, here are other best practices to follow:
|
||||
|
||||
##### Providing an enable / disable option:
|
||||
For a better user experience, provide a way to disable the plugin without uninstalling it. This is especially important if your plugin adds additional webpack aliases, this will allow you to still let the webpack run to prevent errors.
|
||||
##### Include tests in your GitHub CI workflow:
|
||||
If you've configured tests for your package, integrate them into your workflow to run the tests each time you commit to the plugin repository. Learn more about [how to configure tests into your GitHub CI workflow.](https://docs.github.com/en/actions/automating-builds-and-tests/building-and-testing-nodejs)
|
||||
##### Publish your finished plugin to NPM:
|
||||
The best way to share and allow others to use your plugin once it is complete is to publish an NPM package. This process is straightforward and well documented, find out more about [creating and publishing a NPM package here](https://docs.npmjs.com/creating-and-publishing-scoped-public-packages/).
|
||||
##### Add payload-plugin topic tag:
|
||||
Apply the tag **payload-plugin** to your GitHub repository. This will boost the visibility of your plugin and ensure it gets listed with [existing payload plugins](https://github.com/topics/payload-plugin).
|
||||
##### Use [Semantic Versioning](https://semver.org/) (SemVar):
|
||||
With the SemVar system you release version numbers that reflect the nature of changes (major, minor, patch). Ensure all major versions reference their Payload compatibility.
|
||||
@@ -160,7 +160,7 @@ DigitalOcean provides extremely helpful documentation that can walk you through
|
||||
|
||||
## Docker
|
||||
|
||||
This is an example of a multi-stage docker build of Payload for production. Ensure you are setting your environment variables on deployment, like `PAYLOAD_SECRET`, `PAYLOAD_CONFIG_PATH`, and `MONGODB_URI` if needed.
|
||||
This is an example of a multi-stage docker build of Payload for production. Ensure you are setting your environment variables on deployment, like `PAYLOAD_SECRET`, `PAYLOAD_CONFIG_PATH`, and `DATABASE_URI` if needed.
|
||||
|
||||
```dockerfile
|
||||
FROM node:18-alpine as base
|
||||
@@ -210,7 +210,7 @@ services:
|
||||
depends_on:
|
||||
- mongo
|
||||
environment:
|
||||
MONGODB_URI: mongodb://mongo:27017/payload
|
||||
DATABASE_URI: mongodb://mongo:27017/payload
|
||||
PORT: 3000
|
||||
NODE_ENV: development
|
||||
PAYLOAD_SECRET: TESTING
|
||||
|
||||
@@ -59,3 +59,7 @@ All Payload APIs support the pagination controls below. With them, you can creat
|
||||
| ------- | --------------------------------------- |
|
||||
| `limit` | Limits the number of documents returned |
|
||||
| `page` | Get a specific page number |
|
||||
|
||||
### Disabling pagination within Local API
|
||||
|
||||
For `find` operations within the Local API, you can disable pagination to retrieve all documents from a collection by passing `pagination: false` to the `find` local operation. This is not supported in REST or GraphQL, however, because it could potentially lead to malicious activity.
|
||||
@@ -1,4 +1,5 @@
|
||||
{
|
||||
"ext": "ts",
|
||||
"exec": "ts-node src/server.ts"
|
||||
"exec": "ts-node src/server.ts -- -I",
|
||||
"stdin": false
|
||||
}
|
||||
|
||||
@@ -17,9 +17,9 @@
|
||||
"lint:fix": "eslint --fix --ext .ts,.tsx src"
|
||||
},
|
||||
"dependencies": {
|
||||
"@payloadcms/bundler-webpack": "^1.0.0-beta.5",
|
||||
"@payloadcms/db-mongodb": "^1.0.0-beta.8",
|
||||
"@payloadcms/richtext-slate": "^1.0.0-beta.4",
|
||||
"@payloadcms/bundler-webpack": "latest",
|
||||
"@payloadcms/db-mongodb": "latest",
|
||||
"@payloadcms/richtext-slate": "latest",
|
||||
"dotenv": "^8.2.0",
|
||||
"express": "^4.17.1",
|
||||
"payload": "latest"
|
||||
|
||||
File diff suppressed because it is too large
Load Diff
@@ -1,5 +1,6 @@
|
||||
{
|
||||
"watch": ["server.ts"],
|
||||
"exec": "ts-node --project tsconfig.server.json src/server.ts",
|
||||
"ext": "js ts"
|
||||
"exec": "ts-node --project tsconfig.server.json src/server.ts -- -I",
|
||||
"ext": "js ts",
|
||||
"stdin": false
|
||||
}
|
||||
|
||||
@@ -20,9 +20,9 @@
|
||||
"lint:fix": "eslint --fix --ext .ts,.tsx src"
|
||||
},
|
||||
"dependencies": {
|
||||
"@payloadcms/bundler-webpack": "^1.0.0-beta.5",
|
||||
"@payloadcms/db-mongodb": "^1.0.0-beta.8",
|
||||
"@payloadcms/richtext-slate": "^1.0.0-beta.4",
|
||||
"@payloadcms/bundler-webpack": "latest",
|
||||
"@payloadcms/db-mongodb": "latest",
|
||||
"@payloadcms/richtext-slate": "latest",
|
||||
"dotenv": "^8.2.0",
|
||||
"escape-html": "^1.0.3",
|
||||
"express": "^4.17.1",
|
||||
|
||||
File diff suppressed because it is too large
Load Diff
@@ -1,4 +1,5 @@
|
||||
{
|
||||
"ext": "ts",
|
||||
"exec": "ts-node src/server.ts"
|
||||
"exec": "ts-node src/server.ts -- -I",
|
||||
"stdin": false
|
||||
}
|
||||
|
||||
@@ -18,9 +18,9 @@
|
||||
"lint:fix": "eslint --fix --ext .ts,.tsx src"
|
||||
},
|
||||
"dependencies": {
|
||||
"@payloadcms/bundler-webpack": "^1.0.0-beta.5",
|
||||
"@payloadcms/db-mongodb": "^1.0.0-beta.8",
|
||||
"@payloadcms/richtext-slate": "^1.0.0-beta.4",
|
||||
"@payloadcms/bundler-webpack": "latest",
|
||||
"@payloadcms/db-mongodb": "latest",
|
||||
"@payloadcms/richtext-slate": "latest",
|
||||
"dotenv": "^8.2.0",
|
||||
"express": "^4.17.1",
|
||||
"payload": "latest"
|
||||
|
||||
File diff suppressed because it is too large
Load Diff
@@ -1,4 +1,5 @@
|
||||
{
|
||||
"ext": "ts",
|
||||
"exec": "ts-node src/server.ts"
|
||||
"exec": "ts-node src/server.ts -- -I",
|
||||
"stdin": false
|
||||
}
|
||||
|
||||
@@ -1,4 +1,5 @@
|
||||
{
|
||||
"ext": "ts",
|
||||
"exec": "ts-node src/server.ts"
|
||||
"exec": "ts-node src/server.ts -- -I",
|
||||
"stdin": false
|
||||
}
|
||||
|
||||
@@ -15,9 +15,9 @@
|
||||
"generate:graphQLSchema": "PAYLOAD_CONFIG_PATH=src/payload.config.ts payload generate:graphQLSchema"
|
||||
},
|
||||
"dependencies": {
|
||||
"@payloadcms/bundler-webpack": "^1.0.0-beta.5",
|
||||
"@payloadcms/db-mongodb": "^1.0.0-beta.8",
|
||||
"@payloadcms/richtext-slate": "^1.0.0-beta.4",
|
||||
"@payloadcms/bundler-webpack": "latest",
|
||||
"@payloadcms/db-mongodb": "latest",
|
||||
"@payloadcms/richtext-slate": "latest",
|
||||
"@faceless-ui/modal": "^2.0.1",
|
||||
"@payloadcms/plugin-form-builder": "^1.0.12",
|
||||
"@payloadcms/plugin-seo": "^1.0.8",
|
||||
|
||||
File diff suppressed because it is too large
Load Diff
@@ -18,9 +18,9 @@
|
||||
"lint:fix": "eslint --fix --ext .ts,.tsx src"
|
||||
},
|
||||
"dependencies": {
|
||||
"@payloadcms/bundler-webpack": "^1.0.0-beta.5",
|
||||
"@payloadcms/db-mongodb": "^1.0.0-beta.8",
|
||||
"@payloadcms/richtext-slate": "^1.0.0-beta.4",
|
||||
"@payloadcms/bundler-webpack": "latest",
|
||||
"@payloadcms/db-mongodb": "latest",
|
||||
"@payloadcms/richtext-slate": "latest",
|
||||
"dotenv": "^8.2.0",
|
||||
"express": "^4.17.1",
|
||||
"payload": "latest"
|
||||
|
||||
@@ -10,7 +10,8 @@ export const Pages: CollectionConfig = {
|
||||
useAsTitle: 'title',
|
||||
defaultColumns: ['title', 'slug', 'updatedAt'],
|
||||
livePreview: {
|
||||
url: ({ data }) => `${process.env.PAYLOAD_PUBLIC_SITE_URL}/${data.slug}`,
|
||||
url: ({ data }) =>
|
||||
`${process.env.PAYLOAD_PUBLIC_SITE_URL}${data.slug !== 'home' ? `/${data.slug}` : ''}`,
|
||||
},
|
||||
},
|
||||
access: {
|
||||
|
||||
File diff suppressed because it is too large
Load Diff
@@ -1,4 +1,5 @@
|
||||
{
|
||||
"ext": "ts",
|
||||
"exec": "ts-node src/server.ts"
|
||||
"exec": "ts-node src/server.ts -- -I",
|
||||
"stdin": false
|
||||
}
|
||||
|
||||
@@ -18,9 +18,9 @@
|
||||
"lint:fix": "eslint --fix --ext .ts,.tsx src"
|
||||
},
|
||||
"dependencies": {
|
||||
"@payloadcms/bundler-webpack": "^1.0.0-beta.5",
|
||||
"@payloadcms/db-mongodb": "^1.0.0-beta.8",
|
||||
"@payloadcms/richtext-slate": "^1.0.0-beta.4",
|
||||
"@payloadcms/bundler-webpack": "latest",
|
||||
"@payloadcms/db-mongodb": "latest",
|
||||
"@payloadcms/richtext-slate": "latest",
|
||||
"dotenv": "^8.2.0",
|
||||
"express": "^4.17.1",
|
||||
"payload": "latest"
|
||||
|
||||
File diff suppressed because it is too large
Load Diff
@@ -1,4 +1,5 @@
|
||||
{
|
||||
"ext": "ts",
|
||||
"exec": "ts-node src/server.ts"
|
||||
"exec": "ts-node src/server.ts -- -I",
|
||||
"stdin": false
|
||||
}
|
||||
|
||||
@@ -17,9 +17,9 @@
|
||||
"lint:fix": "eslint --fix --ext .ts,.tsx src"
|
||||
},
|
||||
"dependencies": {
|
||||
"@payloadcms/bundler-webpack": "^1.0.0-beta.5",
|
||||
"@payloadcms/db-mongodb": "^1.0.0-beta.8",
|
||||
"@payloadcms/richtext-slate": "^1.0.0-beta.4",
|
||||
"@payloadcms/bundler-webpack": "latest",
|
||||
"@payloadcms/db-mongodb": "latest",
|
||||
"@payloadcms/richtext-slate": "latest",
|
||||
"@payloadcms/plugin-redirects": "^1.0.0",
|
||||
"dotenv": "^8.2.0",
|
||||
"express": "^4.17.1",
|
||||
|
||||
File diff suppressed because it is too large
Load Diff
@@ -1,4 +1,5 @@
|
||||
{
|
||||
"ext": "ts",
|
||||
"exec": "ts-node src/server.ts"
|
||||
"exec": "ts-node src/server.ts -- -I",
|
||||
"stdin": false
|
||||
}
|
||||
|
||||
@@ -1,4 +1,5 @@
|
||||
{
|
||||
"ext": "ts",
|
||||
"exec": "ts-node src/server.ts"
|
||||
"exec": "ts-node src/server.ts -- -I",
|
||||
"stdin": false
|
||||
}
|
||||
|
||||
10
package.json
10
package.json
@@ -20,8 +20,8 @@
|
||||
"lint-staged": "lint-staged",
|
||||
"pretest": "pnpm build",
|
||||
"reinstall": "pnpm clean:unix && pnpm install",
|
||||
"list:packages": "./scripts/list_published_packages.sh beta",
|
||||
"script:release:beta": "./scripts/release_beta.sh",
|
||||
"script:list-packages": "tsx ./scripts/list-packages.ts",
|
||||
"script:release": "tsx ./scripts/release.ts",
|
||||
"test": "pnpm test:int && pnpm test:components && pnpm test:e2e",
|
||||
"test:components": "cross-env jest --config=jest.components.config.js",
|
||||
"test:e2e": "npx playwright install --with-deps && ts-node -T ./test/runE2E.ts",
|
||||
@@ -34,7 +34,7 @@
|
||||
},
|
||||
"devDependencies": {
|
||||
"@payloadcms/eslint-config": "workspace:*",
|
||||
"@playwright/test": "1.37.1",
|
||||
"@playwright/test": "1.38.1",
|
||||
"@swc/cli": "^0.1.62",
|
||||
"@swc/jest": "0.2.29",
|
||||
"@swc/register": "0.1.10",
|
||||
@@ -50,6 +50,7 @@
|
||||
"@types/shelljs": "0.8.12",
|
||||
"@types/testing-library__jest-dom": "5.14.8",
|
||||
"chalk": "^5.3.0",
|
||||
"chalk-template": "1.1.0",
|
||||
"copyfiles": "2.4.1",
|
||||
"cross-env": "7.0.3",
|
||||
"dotenv": "8.6.0",
|
||||
@@ -58,7 +59,6 @@
|
||||
"fs-extra": "10.1.0",
|
||||
"get-port": "5.1.1",
|
||||
"glob": "8.1.0",
|
||||
"graphql-request": "6.1.0",
|
||||
"husky": "^8.0.3",
|
||||
"isomorphic-fetch": "3.0.0",
|
||||
"jest": "29.6.4",
|
||||
@@ -74,9 +74,11 @@
|
||||
"qs": "6.11.2",
|
||||
"rimraf": "3.0.2",
|
||||
"shelljs": "0.8.5",
|
||||
"simple-git": "^3.20.0",
|
||||
"slash": "3.0.0",
|
||||
"slate": "0.91.4",
|
||||
"ts-node": "10.9.1",
|
||||
"tsx": "^3.13.0",
|
||||
"turbo": "^1.10.15",
|
||||
"typescript": "5.2.2",
|
||||
"uuid": "^9.0.0"
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
{
|
||||
"name": "@payloadcms/bundler-webpack",
|
||||
"version": "1.0.2",
|
||||
"version": "1.0.3",
|
||||
"description": "The officially supported Webpack bundler adapter for Payload",
|
||||
"repository": "https://github.com/payloadcms/payload",
|
||||
"license": "MIT",
|
||||
@@ -25,6 +25,7 @@
|
||||
"css-loader": "5.2.7",
|
||||
"css-minimizer-webpack-plugin": "^5.0.0",
|
||||
"file-loader": "6.2.0",
|
||||
"find-node-modules": "^2.1.3",
|
||||
"html-webpack-plugin": "^5.5.0",
|
||||
"md5": "2.3.0",
|
||||
"mini-css-extract-plugin": "1.6.2",
|
||||
@@ -48,6 +49,7 @@
|
||||
"devDependencies": {
|
||||
"@payloadcms/eslint-config": "workspace:*",
|
||||
"@types/extract-text-webpack-plugin": "^3.0.7",
|
||||
"@types/find-node-modules": "^2.1.0",
|
||||
"@types/html-webpack-plugin": "^3.2.6",
|
||||
"@types/mini-css-extract-plugin": "^1.4.3",
|
||||
"@types/optimize-css-assets-webpack-plugin": "^5.0.5",
|
||||
|
||||
@@ -1,6 +1,7 @@
|
||||
import type { SanitizedConfig } from 'payload/config'
|
||||
import type { Configuration } from 'webpack'
|
||||
|
||||
import findNodeModules from 'find-node-modules'
|
||||
import HtmlWebpackPlugin from 'html-webpack-plugin'
|
||||
import path from 'path'
|
||||
import webpack from 'webpack'
|
||||
@@ -8,7 +9,8 @@ import webpack from 'webpack'
|
||||
const mockModulePath = path.resolve(__dirname, '../mocks/emptyModule.js')
|
||||
const mockDotENVPath = path.resolve(__dirname, '../mocks/dotENV.js')
|
||||
|
||||
const nodeModulesPath = path.resolve(__dirname, '../../../../')
|
||||
const nodeModulesPaths = findNodeModules({ cwd: process.cwd() })
|
||||
const nodeModulesPath = path.resolve(nodeModulesPaths[0])
|
||||
const adminFolderPath = path.resolve(nodeModulesPath, 'payload/dist/admin')
|
||||
|
||||
export const getBaseConfig = (payloadConfig: SanitizedConfig): Configuration => ({
|
||||
|
||||
@@ -23,7 +23,9 @@ export const getDevConfig = (payloadConfig: SanitizedConfig): Configuration => {
|
||||
entry: {
|
||||
...baseConfig.entry,
|
||||
main: [
|
||||
`webpack-hot-middleware/client?path=${payloadConfig.routes.admin}/__webpack_hmr`,
|
||||
`${require.resolve('webpack-hot-middleware/client')}?path=${
|
||||
payloadConfig.routes.admin
|
||||
}/__webpack_hmr`,
|
||||
...(baseConfig.entry.main as string[]),
|
||||
],
|
||||
},
|
||||
|
||||
44
packages/create-payload-app/.eslintrc.js
Normal file
44
packages/create-payload-app/.eslintrc.js
Normal file
@@ -0,0 +1,44 @@
|
||||
/** @type {import('prettier').Config} */
|
||||
module.exports = {
|
||||
extends: ['@payloadcms'],
|
||||
ignorePatterns: ['README.md', '**/*.spec.ts'],
|
||||
overrides: [
|
||||
{
|
||||
extends: ['plugin:@typescript-eslint/disable-type-checked'],
|
||||
files: ['*.js', '*.cjs', '*.json', '*.md', '*.yml', '*.yaml'],
|
||||
},
|
||||
{
|
||||
files: ['**/*.ts', '**/*.tsx'],
|
||||
rules: {
|
||||
'no-console': 'off',
|
||||
},
|
||||
},
|
||||
{
|
||||
files: ['package.json', 'tsconfig.json'],
|
||||
rules: {
|
||||
'perfectionist/sort-array-includes': 'off',
|
||||
'perfectionist/sort-astro-attributes': 'off',
|
||||
'perfectionist/sort-classes': 'off',
|
||||
'perfectionist/sort-enums': 'off',
|
||||
'perfectionist/sort-exports': 'off',
|
||||
'perfectionist/sort-imports': 'off',
|
||||
'perfectionist/sort-interfaces': 'off',
|
||||
'perfectionist/sort-jsx-props': 'off',
|
||||
'perfectionist/sort-keys': 'off',
|
||||
'perfectionist/sort-maps': 'off',
|
||||
'perfectionist/sort-named-exports': 'off',
|
||||
'perfectionist/sort-named-imports': 'off',
|
||||
'perfectionist/sort-object-types': 'off',
|
||||
'perfectionist/sort-objects': 'off',
|
||||
'perfectionist/sort-svelte-attributes': 'off',
|
||||
'perfectionist/sort-union-types': 'off',
|
||||
'perfectionist/sort-vue-attributes': 'off',
|
||||
},
|
||||
},
|
||||
],
|
||||
parserOptions: {
|
||||
project: ['./tsconfig.json'],
|
||||
tsconfigRootDir: __dirname,
|
||||
},
|
||||
root: true,
|
||||
}
|
||||
34
packages/create-payload-app/README.md
Normal file
34
packages/create-payload-app/README.md
Normal file
@@ -0,0 +1,34 @@
|
||||
# Create Payload App
|
||||
|
||||
CLI for easily starting new Payload project
|
||||
|
||||
## Usage
|
||||
|
||||
```text
|
||||
|
||||
USAGE
|
||||
|
||||
$ npx create-payload-app
|
||||
$ npx create-payload-app my-project
|
||||
$ npx create-payload-app -n my-project -t blog
|
||||
|
||||
OPTIONS
|
||||
|
||||
-n my-payload-app Set project name
|
||||
-t template_name Choose specific template
|
||||
|
||||
Available templates:
|
||||
|
||||
blank Blank Template
|
||||
website Website Template
|
||||
ecommerce E-commerce Template
|
||||
plugin Template for creating a Payload plugin
|
||||
payload-demo Payload demo site at https://demo.payloadcms.com
|
||||
payload-website Payload website CMS at https://payloadcms.com
|
||||
|
||||
--use-npm Use npm to install dependencies
|
||||
--use-yarn Use yarn to install dependencies
|
||||
--use-pnpm Use pnpm to install dependencies
|
||||
--no-deps Do not install any dependencies
|
||||
-h Show help
|
||||
```
|
||||
2
packages/create-payload-app/bin/cli.js
Executable file
2
packages/create-payload-app/bin/cli.js
Executable file
@@ -0,0 +1,2 @@
|
||||
#!/usr/bin/env node
|
||||
require('../dist/index.js')
|
||||
9
packages/create-payload-app/jest.config.js
Normal file
9
packages/create-payload-app/jest.config.js
Normal file
@@ -0,0 +1,9 @@
|
||||
module.exports = {
|
||||
testEnvironment: 'node',
|
||||
testMatch: ['**/src/**/?(*.)+(spec|test|it-test).[tj]s?(x)'],
|
||||
testTimeout: 10000,
|
||||
transform: {
|
||||
'^.+\\.(ts|tsx)?$': 'ts-jest',
|
||||
},
|
||||
verbose: true,
|
||||
}
|
||||
47
packages/create-payload-app/package.json
Normal file
47
packages/create-payload-app/package.json
Normal file
@@ -0,0 +1,47 @@
|
||||
{
|
||||
"name": "create-payload-app",
|
||||
"version": "0.5.2",
|
||||
"license": "MIT",
|
||||
"bin": {
|
||||
"create-payload-app": "bin/cli.js"
|
||||
},
|
||||
"scripts": {
|
||||
"build": "tsc && pnpm copyfiles",
|
||||
"copyfiles": "copyfiles -u 1 \"src/templates/**\" \"src/lib/common-files/**\" dist",
|
||||
"clean": "rimraf dist",
|
||||
"typecheck": "tsc --noEmit",
|
||||
"lint": "eslint \"src/**/*.ts\"",
|
||||
"lint:fix": "eslint \"src/**/*.ts\" --fix",
|
||||
"lint-staged": "lint-staged --quiet",
|
||||
"test": "jest",
|
||||
"prepublishOnly": "pnpm test && pnpm clean && pnpm build"
|
||||
},
|
||||
"files": [
|
||||
"package.json",
|
||||
"dist",
|
||||
"bin"
|
||||
],
|
||||
"dependencies": {
|
||||
"@sindresorhus/slugify": "^1.1.0",
|
||||
"arg": "^5.0.0",
|
||||
"chalk": "^4.1.0",
|
||||
"command-exists": "^1.2.9",
|
||||
"degit": "^2.8.4",
|
||||
"execa": "^5.0.0",
|
||||
"figures": "^3.2.0",
|
||||
"fs-extra": "^9.0.1",
|
||||
"handlebars": "^4.7.7",
|
||||
"ora": "^5.1.0",
|
||||
"prompts": "^2.4.2",
|
||||
"terminal-link": "^2.1.1"
|
||||
},
|
||||
"devDependencies": {
|
||||
"@types/command-exists": "^1.2.0",
|
||||
"@types/degit": "^2.8.3",
|
||||
"@types/fs-extra": "^9.0.12",
|
||||
"@types/jest": "^27.0.3",
|
||||
"@types/node": "^16.6.2",
|
||||
"@types/prompts": "^2.4.1",
|
||||
"ts-jest": "^29.1.0"
|
||||
}
|
||||
}
|
||||
8
packages/create-payload-app/src/index.ts
Normal file
8
packages/create-payload-app/src/index.ts
Normal file
@@ -0,0 +1,8 @@
|
||||
import { Main } from './main'
|
||||
import { error } from './utils/log'
|
||||
|
||||
async function main(): Promise<void> {
|
||||
await new Main().init()
|
||||
}
|
||||
|
||||
main().catch((e) => error(`An error has occurred: ${e instanceof Error ? e.message : e}`))
|
||||
117
packages/create-payload-app/src/lib/configure-payload-config.ts
Normal file
117
packages/create-payload-app/src/lib/configure-payload-config.ts
Normal file
@@ -0,0 +1,117 @@
|
||||
import fse from 'fs-extra'
|
||||
import path from 'path'
|
||||
|
||||
import type { DbDetails } from '../types'
|
||||
|
||||
import { warning } from '../utils/log'
|
||||
import { bundlerPackages, dbPackages, editorPackages } from './packages'
|
||||
|
||||
/** Update payload config with necessary imports and adapters */
|
||||
export async function configurePayloadConfig(args: {
|
||||
dbDetails: DbDetails | undefined
|
||||
projectDir: string
|
||||
}): Promise<void> {
|
||||
if (!args.dbDetails) {
|
||||
return
|
||||
}
|
||||
|
||||
// Update package.json
|
||||
const packageJsonPath = path.resolve(args.projectDir, 'package.json')
|
||||
try {
|
||||
const packageObj = await fse.readJson(packageJsonPath)
|
||||
|
||||
packageObj.dependencies['payload'] = '^2.0.0'
|
||||
|
||||
const dbPackage = dbPackages[args.dbDetails.type]
|
||||
const bundlerPackage = bundlerPackages['webpack']
|
||||
const editorPackage = editorPackages['slate']
|
||||
|
||||
// Delete all other db adapters
|
||||
Object.values(dbPackages).forEach((p) => {
|
||||
if (p.packageName !== dbPackage.packageName) {
|
||||
delete packageObj.dependencies[p.packageName]
|
||||
}
|
||||
})
|
||||
|
||||
packageObj.dependencies[dbPackage.packageName] = dbPackage.version
|
||||
packageObj.dependencies[bundlerPackage.packageName] = bundlerPackage.version
|
||||
packageObj.dependencies[editorPackage.packageName] = editorPackage.version
|
||||
|
||||
await fse.writeJson(packageJsonPath, packageObj, { spaces: 2 })
|
||||
} catch (err: unknown) {
|
||||
warning('Unable to update name in package.json')
|
||||
}
|
||||
|
||||
try {
|
||||
const possiblePaths = [
|
||||
path.resolve(args.projectDir, 'src/payload.config.ts'),
|
||||
path.resolve(args.projectDir, 'src/payload/payload.config.ts'),
|
||||
]
|
||||
|
||||
let payloadConfigPath: string | undefined
|
||||
|
||||
possiblePaths.forEach((p) => {
|
||||
if (fse.pathExistsSync(p) && !payloadConfigPath) {
|
||||
payloadConfigPath = p
|
||||
}
|
||||
})
|
||||
|
||||
if (!payloadConfigPath) {
|
||||
warning('Unable to update payload.config.ts with plugins')
|
||||
return
|
||||
}
|
||||
|
||||
const configContent = fse.readFileSync(payloadConfigPath, 'utf-8')
|
||||
const configLines = configContent.split('\n')
|
||||
|
||||
const dbReplacement = dbPackages[args.dbDetails.type]
|
||||
const bundlerReplacement = bundlerPackages['webpack']
|
||||
const editorReplacement = editorPackages['slate']
|
||||
|
||||
let dbConfigStartLineIndex: number | undefined
|
||||
let dbConfigEndLineIndex: number | undefined
|
||||
|
||||
configLines.forEach((l, i) => {
|
||||
if (l.includes('// database-adapter-import')) {
|
||||
configLines[i] = dbReplacement.importReplacement
|
||||
}
|
||||
if (l.includes('// bundler-import')) {
|
||||
configLines[i] = bundlerReplacement.importReplacement
|
||||
}
|
||||
|
||||
if (l.includes('// bundler-config')) {
|
||||
configLines[i] = bundlerReplacement.configReplacement
|
||||
}
|
||||
|
||||
if (l.includes('// editor-import')) {
|
||||
configLines[i] = editorReplacement.importReplacement
|
||||
}
|
||||
|
||||
if (l.includes('// editor-config')) {
|
||||
configLines[i] = editorReplacement.configReplacement
|
||||
}
|
||||
|
||||
if (l.includes('// database-adapter-config-start')) {
|
||||
dbConfigStartLineIndex = i
|
||||
}
|
||||
if (l.includes('// database-adapter-config-end')) {
|
||||
dbConfigEndLineIndex = i
|
||||
}
|
||||
})
|
||||
|
||||
if (!dbConfigStartLineIndex || !dbConfigEndLineIndex) {
|
||||
warning('Unable to update payload.config.ts with database adapter import')
|
||||
} else {
|
||||
// Replaces lines between `// database-adapter-config-start` and `// database-adapter-config-end`
|
||||
configLines.splice(
|
||||
dbConfigStartLineIndex,
|
||||
dbConfigEndLineIndex - dbConfigStartLineIndex + 1,
|
||||
...dbReplacement.configReplacement,
|
||||
)
|
||||
}
|
||||
|
||||
fse.writeFileSync(payloadConfigPath, configLines.join('\n'))
|
||||
} catch (err: unknown) {
|
||||
warning('Unable to update payload.config.ts with plugins')
|
||||
}
|
||||
}
|
||||
151
packages/create-payload-app/src/lib/create-project.spec.ts
Normal file
151
packages/create-payload-app/src/lib/create-project.spec.ts
Normal file
@@ -0,0 +1,151 @@
|
||||
import fse from 'fs-extra'
|
||||
import path from 'path'
|
||||
import type { BundlerType, CliArgs, DbType, ProjectTemplate } from '../types'
|
||||
import { createProject } from './create-project'
|
||||
import { bundlerPackages, dbPackages, editorPackages } from './packages'
|
||||
import exp from 'constants'
|
||||
|
||||
const projectDir = path.resolve(__dirname, './tmp')
|
||||
describe('createProject', () => {
|
||||
beforeAll(() => {
|
||||
console.log = jest.fn()
|
||||
})
|
||||
|
||||
beforeEach(() => {
|
||||
if (fse.existsSync(projectDir)) {
|
||||
fse.rmdirSync(projectDir, { recursive: true })
|
||||
}
|
||||
})
|
||||
afterEach(() => {
|
||||
if (fse.existsSync(projectDir)) {
|
||||
fse.rmSync(projectDir, { recursive: true })
|
||||
}
|
||||
})
|
||||
|
||||
describe('#createProject', () => {
|
||||
// eslint-disable-next-line @typescript-eslint/consistent-type-assertions
|
||||
const args = {
|
||||
_: ['project-name'],
|
||||
'--db': 'mongodb',
|
||||
'--no-deps': true,
|
||||
} as CliArgs
|
||||
const packageManager = 'yarn'
|
||||
|
||||
it('creates starter project', async () => {
|
||||
const projectName = 'starter-project'
|
||||
const template: ProjectTemplate = {
|
||||
name: 'blank',
|
||||
type: 'starter',
|
||||
url: 'https://github.com/payloadcms/payload/templates/blank',
|
||||
description: 'Blank Template',
|
||||
}
|
||||
await createProject({
|
||||
cliArgs: args,
|
||||
projectName,
|
||||
projectDir,
|
||||
template,
|
||||
packageManager,
|
||||
})
|
||||
|
||||
const packageJsonPath = path.resolve(projectDir, 'package.json')
|
||||
const packageJson = fse.readJsonSync(packageJsonPath)
|
||||
|
||||
// Check package name and description
|
||||
expect(packageJson.name).toEqual(projectName)
|
||||
})
|
||||
|
||||
it('creates plugin template', async () => {
|
||||
const projectName = 'plugin'
|
||||
const template: ProjectTemplate = {
|
||||
name: 'plugin',
|
||||
type: 'plugin',
|
||||
url: 'https://github.com/payloadcms/payload-plugin-template',
|
||||
description: 'Template for creating a Payload plugin',
|
||||
}
|
||||
await createProject({
|
||||
cliArgs: args,
|
||||
projectName,
|
||||
projectDir,
|
||||
template,
|
||||
packageManager,
|
||||
})
|
||||
|
||||
const packageJsonPath = path.resolve(projectDir, 'package.json')
|
||||
const packageJson = fse.readJsonSync(packageJsonPath)
|
||||
|
||||
// Check package name and description
|
||||
expect(packageJson.name).toEqual(projectName)
|
||||
})
|
||||
|
||||
describe('db adapters and bundlers', () => {
|
||||
it.each([
|
||||
['mongodb', 'webpack'],
|
||||
['postgres', 'webpack'],
|
||||
])('update config and deps: %s, %s', async (db, bundler) => {
|
||||
const projectName = 'starter-project'
|
||||
const template: ProjectTemplate = {
|
||||
name: 'blank',
|
||||
type: 'starter',
|
||||
url: 'https://github.com/payloadcms/payload/templates/blank',
|
||||
description: 'Blank Template',
|
||||
}
|
||||
await createProject({
|
||||
cliArgs: args,
|
||||
projectName,
|
||||
projectDir,
|
||||
template,
|
||||
packageManager,
|
||||
dbDetails: {
|
||||
dbUri: `${db}://localhost:27017/create-project-test`,
|
||||
type: db as DbType,
|
||||
},
|
||||
})
|
||||
|
||||
const dbReplacement = dbPackages[db as DbType]
|
||||
const bundlerReplacement = bundlerPackages[bundler as BundlerType]
|
||||
const editorReplacement = editorPackages['slate']
|
||||
|
||||
const packageJsonPath = path.resolve(projectDir, 'package.json')
|
||||
const packageJson = fse.readJsonSync(packageJsonPath)
|
||||
|
||||
// Check deps
|
||||
expect(packageJson.dependencies['payload']).toEqual('^2.0.0')
|
||||
expect(packageJson.dependencies[dbReplacement.packageName]).toEqual(dbReplacement.version)
|
||||
|
||||
// Should only have one db adapter
|
||||
expect(
|
||||
Object.keys(packageJson.dependencies).filter((n) => n.startsWith('@payloadcms/db-')),
|
||||
).toHaveLength(1)
|
||||
|
||||
expect(packageJson.dependencies[bundlerReplacement.packageName]).toEqual(
|
||||
bundlerReplacement.version,
|
||||
)
|
||||
expect(packageJson.dependencies[editorReplacement.packageName]).toEqual(
|
||||
editorReplacement.version,
|
||||
)
|
||||
|
||||
const payloadConfigPath = path.resolve(projectDir, 'src/payload.config.ts')
|
||||
const content = fse.readFileSync(payloadConfigPath, 'utf-8')
|
||||
|
||||
// Check payload.config.ts
|
||||
expect(content).not.toContain('// database-adapter-import')
|
||||
expect(content).toContain(dbReplacement.importReplacement)
|
||||
|
||||
expect(content).not.toContain('// database-adapter-config-start')
|
||||
expect(content).not.toContain('// database-adapter-config-end')
|
||||
expect(content).toContain(dbReplacement.configReplacement.join('\n'))
|
||||
|
||||
expect(content).not.toContain('// bundler-config-import')
|
||||
expect(content).toContain(bundlerReplacement.importReplacement)
|
||||
|
||||
expect(content).not.toContain('// bundler-config')
|
||||
expect(content).toContain(bundlerReplacement.configReplacement)
|
||||
})
|
||||
})
|
||||
})
|
||||
|
||||
describe('Templates', () => {
|
||||
it.todo('Verify that all templates are valid')
|
||||
// Loop through all templates.ts that should have replacement comments, and verify that they are present
|
||||
})
|
||||
})
|
||||
102
packages/create-payload-app/src/lib/create-project.ts
Normal file
102
packages/create-payload-app/src/lib/create-project.ts
Normal file
@@ -0,0 +1,102 @@
|
||||
import chalk from 'chalk'
|
||||
import degit from 'degit'
|
||||
import execa from 'execa'
|
||||
import fse from 'fs-extra'
|
||||
import ora from 'ora'
|
||||
import path from 'path'
|
||||
|
||||
import type { CliArgs, DbDetails, PackageManager, ProjectTemplate } from '../types'
|
||||
|
||||
import { error, success, warning } from '../utils/log'
|
||||
import { configurePayloadConfig } from './configure-payload-config'
|
||||
|
||||
async function createOrFindProjectDir(projectDir: string): Promise<void> {
|
||||
const pathExists = await fse.pathExists(projectDir)
|
||||
if (!pathExists) {
|
||||
await fse.mkdir(projectDir)
|
||||
}
|
||||
}
|
||||
|
||||
async function installDeps(args: {
|
||||
cliArgs: CliArgs
|
||||
packageManager: PackageManager
|
||||
projectDir: string
|
||||
}): Promise<boolean> {
|
||||
const { cliArgs, packageManager, projectDir } = args
|
||||
if (cliArgs['--no-deps']) {
|
||||
return true
|
||||
}
|
||||
let installCmd = 'npm install --legacy-peer-deps'
|
||||
|
||||
if (packageManager === 'yarn') {
|
||||
installCmd = 'yarn'
|
||||
} else if (packageManager === 'pnpm') {
|
||||
installCmd = 'pnpm install'
|
||||
}
|
||||
|
||||
try {
|
||||
await execa.command(installCmd, {
|
||||
cwd: path.resolve(projectDir),
|
||||
})
|
||||
return true
|
||||
} catch (err: unknown) {
|
||||
console.log({ err })
|
||||
return false
|
||||
}
|
||||
}
|
||||
|
||||
export async function createProject(args: {
|
||||
cliArgs: CliArgs
|
||||
dbDetails?: DbDetails
|
||||
packageManager: PackageManager
|
||||
projectDir: string
|
||||
projectName: string
|
||||
template: ProjectTemplate
|
||||
}): Promise<void> {
|
||||
const { cliArgs, dbDetails, packageManager, projectDir, projectName, template } = args
|
||||
|
||||
await createOrFindProjectDir(projectDir)
|
||||
|
||||
console.log(`\n Creating project in ${chalk.green(path.resolve(projectDir))}\n`)
|
||||
|
||||
if ('url' in template) {
|
||||
const emitter = degit(template.url)
|
||||
await emitter.clone(projectDir)
|
||||
}
|
||||
|
||||
const spinner = ora('Checking latest Payload version...').start()
|
||||
|
||||
await updatePackageJSON({ projectDir, projectName })
|
||||
await configurePayloadConfig({ dbDetails, projectDir })
|
||||
|
||||
// Remove yarn.lock file. This is only desired in Payload Cloud.
|
||||
const lockPath = path.resolve(projectDir, 'yarn.lock')
|
||||
if (fse.existsSync(lockPath)) {
|
||||
await fse.remove(lockPath)
|
||||
}
|
||||
|
||||
spinner.text = 'Installing dependencies...'
|
||||
const result = await installDeps({ cliArgs, packageManager, projectDir })
|
||||
spinner.stop()
|
||||
spinner.clear()
|
||||
if (result) {
|
||||
success('Dependencies installed')
|
||||
} else {
|
||||
error('Error installing dependencies')
|
||||
}
|
||||
}
|
||||
|
||||
export async function updatePackageJSON(args: {
|
||||
projectDir: string
|
||||
projectName: string
|
||||
}): Promise<void> {
|
||||
const { projectDir, projectName } = args
|
||||
const packageJsonPath = path.resolve(projectDir, 'package.json')
|
||||
try {
|
||||
const packageObj = await fse.readJson(packageJsonPath)
|
||||
packageObj.name = projectName
|
||||
await fse.writeJson(packageJsonPath, packageObj, { spaces: 2 })
|
||||
} catch (err: unknown) {
|
||||
warning('Unable to update name in package.json')
|
||||
}
|
||||
}
|
||||
5
packages/create-payload-app/src/lib/generate-secret.ts
Normal file
5
packages/create-payload-app/src/lib/generate-secret.ts
Normal file
@@ -0,0 +1,5 @@
|
||||
import { randomBytes } from 'crypto'
|
||||
|
||||
export function generateSecret(): string {
|
||||
return randomBytes(32).toString('hex').slice(0, 24)
|
||||
}
|
||||
83
packages/create-payload-app/src/lib/packages.ts
Normal file
83
packages/create-payload-app/src/lib/packages.ts
Normal file
@@ -0,0 +1,83 @@
|
||||
import type { BundlerType, DbType, EditorType } from '../types'
|
||||
|
||||
type DbAdapterReplacement = {
|
||||
configReplacement: string[]
|
||||
importReplacement: string
|
||||
packageName: string
|
||||
version: string
|
||||
}
|
||||
|
||||
type BundlerReplacement = {
|
||||
configReplacement: string
|
||||
importReplacement: string
|
||||
packageName: string
|
||||
version: string
|
||||
}
|
||||
|
||||
type EditorReplacement = {
|
||||
configReplacement: string
|
||||
importReplacement: string
|
||||
packageName: string
|
||||
version: string
|
||||
}
|
||||
|
||||
const mongodbReplacement: DbAdapterReplacement = {
|
||||
importReplacement: "import { mongooseAdapter } from '@payloadcms/db-mongodb'",
|
||||
packageName: '@payloadcms/db-mongodb',
|
||||
// Replacement between `// database-adapter-config-start` and `// database-adapter-config-end`
|
||||
configReplacement: [' db: mongooseAdapter({', ' url: process.env.DATABASE_URI,', ' }),'],
|
||||
version: '^1.0.0',
|
||||
}
|
||||
|
||||
const postgresReplacement: DbAdapterReplacement = {
|
||||
configReplacement: [
|
||||
' db: postgresAdapter({',
|
||||
' pool: {',
|
||||
' connectionString: process.env.DATABASE_URI,',
|
||||
' },',
|
||||
' }),',
|
||||
],
|
||||
importReplacement: "import { postgresAdapter } from '@payloadcms/db-postgres'",
|
||||
packageName: '@payloadcms/db-postgres',
|
||||
version: '^0.x', // up to, not including 1.0.0
|
||||
}
|
||||
|
||||
export const dbPackages: Record<DbType, DbAdapterReplacement> = {
|
||||
mongodb: mongodbReplacement,
|
||||
postgres: postgresReplacement,
|
||||
}
|
||||
|
||||
const webpackReplacement: BundlerReplacement = {
|
||||
importReplacement: "import { webpackBundler } from '@payloadcms/bundler-webpack'",
|
||||
packageName: '@payloadcms/bundler-webpack',
|
||||
// Replacement of line containing `// bundler-config`
|
||||
configReplacement: ' bundler: webpackBundler(),',
|
||||
version: '^1.0.0',
|
||||
}
|
||||
|
||||
const viteReplacement: BundlerReplacement = {
|
||||
configReplacement: ' bundler: viteBundler(),',
|
||||
importReplacement: "import { viteBundler } from '@payloadcms/bundler-vite'",
|
||||
packageName: '@payloadcms/bundler-vite',
|
||||
version: '^0.x', // up to, not including 1.0.0
|
||||
}
|
||||
|
||||
export const bundlerPackages: Record<BundlerType, BundlerReplacement> = {
|
||||
vite: viteReplacement,
|
||||
webpack: webpackReplacement,
|
||||
}
|
||||
|
||||
export const editorPackages: Record<EditorType, EditorReplacement> = {
|
||||
lexical: {
|
||||
configReplacement: ' editor: lexicalEditor({}),',
|
||||
importReplacement: "import { lexicalEditor } from '@payloadcms/richtext-lexical'",
|
||||
packageName: '@payloadcms/richtext-lexical',
|
||||
version: '^0.x', // up to, not including 1.0.0
|
||||
},
|
||||
slate: {
|
||||
configReplacement: ' editor: slateEditor({}),',
|
||||
importReplacement: "import { slateEditor } from '@payloadcms/richtext-slate'",
|
||||
packageName: '@payloadcms/richtext-slate',
|
||||
version: '^1.0.0',
|
||||
},
|
||||
}
|
||||
24
packages/create-payload-app/src/lib/parse-project-name.ts
Normal file
24
packages/create-payload-app/src/lib/parse-project-name.ts
Normal file
@@ -0,0 +1,24 @@
|
||||
import prompts from 'prompts'
|
||||
|
||||
import type { CliArgs } from '../types'
|
||||
|
||||
export async function parseProjectName(args: CliArgs): Promise<string> {
|
||||
if (args['--name']) return args['--name']
|
||||
if (args._[0]) return args._[0]
|
||||
|
||||
const response = await prompts(
|
||||
{
|
||||
name: 'value',
|
||||
message: 'Project name?',
|
||||
type: 'text',
|
||||
validate: (value: string) => !!value.length,
|
||||
},
|
||||
{
|
||||
onCancel: () => {
|
||||
process.exit(0)
|
||||
},
|
||||
},
|
||||
)
|
||||
|
||||
return response.value
|
||||
}
|
||||
41
packages/create-payload-app/src/lib/parse-template.ts
Normal file
41
packages/create-payload-app/src/lib/parse-template.ts
Normal file
@@ -0,0 +1,41 @@
|
||||
import prompts from 'prompts'
|
||||
|
||||
import type { CliArgs, ProjectTemplate } from '../types'
|
||||
|
||||
export async function parseTemplate(
|
||||
args: CliArgs,
|
||||
validTemplates: ProjectTemplate[],
|
||||
): Promise<ProjectTemplate> {
|
||||
if (args['--template']) {
|
||||
const templateName = args['--template']
|
||||
const template = validTemplates.find((t) => t.name === templateName)
|
||||
if (!template) throw new Error('Invalid template given')
|
||||
return template
|
||||
}
|
||||
|
||||
const response = await prompts(
|
||||
{
|
||||
name: 'value',
|
||||
choices: validTemplates.map((p) => {
|
||||
return {
|
||||
description: p.description,
|
||||
title: p.name,
|
||||
value: p.name,
|
||||
}
|
||||
}),
|
||||
message: 'Choose project template',
|
||||
type: 'select',
|
||||
validate: (value: string) => !!value.length,
|
||||
},
|
||||
{
|
||||
onCancel: () => {
|
||||
process.exit(0)
|
||||
},
|
||||
},
|
||||
)
|
||||
|
||||
const template = validTemplates.find((t) => t.name === response.value)
|
||||
if (!template) throw new Error('Template is undefined')
|
||||
|
||||
return template
|
||||
}
|
||||
86
packages/create-payload-app/src/lib/select-db.ts
Normal file
86
packages/create-payload-app/src/lib/select-db.ts
Normal file
@@ -0,0 +1,86 @@
|
||||
import slugify from '@sindresorhus/slugify'
|
||||
import prompts from 'prompts'
|
||||
|
||||
import type { CliArgs, DbDetails, DbType } from '../types'
|
||||
|
||||
type DbChoice = {
|
||||
dbConnectionPrefix: `${string}/`
|
||||
title: string
|
||||
value: DbType
|
||||
}
|
||||
|
||||
const dbChoiceRecord: Record<DbType, DbChoice> = {
|
||||
mongodb: {
|
||||
dbConnectionPrefix: 'mongodb://127.0.0.1/',
|
||||
title: 'MongoDB',
|
||||
value: 'mongodb',
|
||||
},
|
||||
postgres: {
|
||||
dbConnectionPrefix: 'postgres://127.0.0.1:5432/',
|
||||
title: 'PostgreSQL (beta)',
|
||||
value: 'postgres',
|
||||
},
|
||||
}
|
||||
|
||||
export async function selectDb(args: CliArgs, projectName: string): Promise<DbDetails> {
|
||||
let dbType: DbType | undefined = undefined
|
||||
if (args['--db']) {
|
||||
if (!Object.values(dbChoiceRecord).some((dbChoice) => dbChoice.value === args['--db'])) {
|
||||
throw new Error(
|
||||
`Invalid database type given. Valid types are: ${Object.values(dbChoiceRecord)
|
||||
.map((dbChoice) => dbChoice.value)
|
||||
.join(', ')}`,
|
||||
)
|
||||
}
|
||||
dbType = args['--db'] as DbType
|
||||
} else {
|
||||
const dbTypeRes = await prompts(
|
||||
{
|
||||
name: 'value',
|
||||
choices: Object.values(dbChoiceRecord).map((dbChoice) => {
|
||||
return {
|
||||
title: dbChoice.title,
|
||||
value: dbChoice.value,
|
||||
}
|
||||
}),
|
||||
message: 'Select a database',
|
||||
type: 'select',
|
||||
validate: (value: string) => !!value.length,
|
||||
},
|
||||
{
|
||||
onCancel: () => {
|
||||
process.exit(0)
|
||||
},
|
||||
},
|
||||
)
|
||||
dbType = dbTypeRes.value
|
||||
}
|
||||
|
||||
const dbChoice = dbChoiceRecord[dbType]
|
||||
|
||||
const dbUriRes = await prompts(
|
||||
{
|
||||
name: 'value',
|
||||
initial: `${dbChoice.dbConnectionPrefix}${
|
||||
projectName === '.' ? `payload-${getRandomDigitSuffix()}` : slugify(projectName)
|
||||
}`,
|
||||
message: `Enter ${dbChoice.title.split(' ')[0]} connection string`, // strip beta from title
|
||||
type: 'text',
|
||||
validate: (value: string) => !!value.length,
|
||||
},
|
||||
{
|
||||
onCancel: () => {
|
||||
process.exit(0)
|
||||
},
|
||||
},
|
||||
)
|
||||
|
||||
return {
|
||||
dbUri: dbUriRes.value,
|
||||
type: dbChoice.value,
|
||||
}
|
||||
}
|
||||
|
||||
function getRandomDigitSuffix(): string {
|
||||
return (Math.random() * Math.pow(10, 6)).toFixed(0)
|
||||
}
|
||||
54
packages/create-payload-app/src/lib/templates.ts
Normal file
54
packages/create-payload-app/src/lib/templates.ts
Normal file
@@ -0,0 +1,54 @@
|
||||
import type { ProjectTemplate } from '../types'
|
||||
|
||||
import { error, info } from '../utils/log'
|
||||
|
||||
export function validateTemplate(templateName: string): boolean {
|
||||
const validTemplates = getValidTemplates()
|
||||
if (!validTemplates.map((t) => t.name).includes(templateName)) {
|
||||
error(`'${templateName}' is not a valid template.`)
|
||||
info(`Valid templates: ${validTemplates.map((t) => t.name).join(', ')}`)
|
||||
return false
|
||||
}
|
||||
return true
|
||||
}
|
||||
|
||||
export function getValidTemplates(): ProjectTemplate[] {
|
||||
return [
|
||||
{
|
||||
name: 'blank',
|
||||
description: 'Blank Template',
|
||||
type: 'starter',
|
||||
url: 'https://github.com/payloadcms/payload/templates/blank',
|
||||
},
|
||||
{
|
||||
name: 'website',
|
||||
description: 'Website Template',
|
||||
type: 'starter',
|
||||
url: 'https://github.com/payloadcms/payload/templates/website',
|
||||
},
|
||||
{
|
||||
name: 'ecommerce',
|
||||
description: 'E-commerce Template',
|
||||
type: 'starter',
|
||||
url: 'https://github.com/payloadcms/payload/templates/ecommerce',
|
||||
},
|
||||
{
|
||||
name: 'plugin',
|
||||
description: 'Template for creating a Payload plugin',
|
||||
type: 'plugin',
|
||||
url: 'https://github.com/payloadcms/payload-plugin-template',
|
||||
},
|
||||
{
|
||||
name: 'payload-demo',
|
||||
description: 'Payload demo site at https://demo.payloadcms.com',
|
||||
type: 'starter',
|
||||
url: 'https://github.com/payloadcms/public-demo',
|
||||
},
|
||||
{
|
||||
name: 'payload-website',
|
||||
description: 'Payload website CMS at https://payloadcms.com',
|
||||
type: 'starter',
|
||||
url: 'https://github.com/payloadcms/website-cms',
|
||||
},
|
||||
]
|
||||
}
|
||||
55
packages/create-payload-app/src/lib/write-env-file.ts
Normal file
55
packages/create-payload-app/src/lib/write-env-file.ts
Normal file
@@ -0,0 +1,55 @@
|
||||
import fs from 'fs-extra'
|
||||
import path from 'path'
|
||||
|
||||
import type { ProjectTemplate } from '../types'
|
||||
|
||||
import { error, success } from '../utils/log'
|
||||
|
||||
/** Parse and swap .env.example values and write .env */
|
||||
export async function writeEnvFile(args: {
|
||||
databaseUri: string
|
||||
payloadSecret: string
|
||||
projectDir: string
|
||||
template: ProjectTemplate
|
||||
}): Promise<void> {
|
||||
const { databaseUri, payloadSecret, projectDir, template } = args
|
||||
try {
|
||||
if (template.type === 'starter' && fs.existsSync(path.join(projectDir, '.env.example'))) {
|
||||
// Parse .env file into key/value pairs
|
||||
const envFile = await fs.readFile(path.join(projectDir, '.env.example'), 'utf8')
|
||||
const envWithValues: string[] = envFile
|
||||
.split('\n')
|
||||
.filter((e) => e)
|
||||
.map((line) => {
|
||||
if (line.startsWith('#') || !line.includes('=')) return line
|
||||
|
||||
const split = line.split('=')
|
||||
const key = split[0]
|
||||
let value = split[1]
|
||||
|
||||
if (key === 'MONGODB_URI' || key === 'MONGO_URL' || key === 'DATABASE_URI') {
|
||||
value = databaseUri
|
||||
}
|
||||
if (key === 'PAYLOAD_SECRET' || key === 'PAYLOAD_SECRET_KEY') {
|
||||
value = payloadSecret
|
||||
}
|
||||
|
||||
return `${key}=${value}`
|
||||
})
|
||||
|
||||
// Write new .env file
|
||||
await fs.writeFile(path.join(projectDir, '.env'), envWithValues.join('\n'))
|
||||
} else {
|
||||
const content = `MONGODB_URI=${databaseUri}\nPAYLOAD_SECRET=${payloadSecret}`
|
||||
await fs.outputFile(`${projectDir}/.env`, content)
|
||||
}
|
||||
|
||||
success('.env file created')
|
||||
} catch (err: unknown) {
|
||||
error('Unable to write .env file')
|
||||
if (err instanceof Error) {
|
||||
error(err.message)
|
||||
}
|
||||
process.exit(1)
|
||||
}
|
||||
}
|
||||
133
packages/create-payload-app/src/main.ts
Normal file
133
packages/create-payload-app/src/main.ts
Normal file
@@ -0,0 +1,133 @@
|
||||
import slugify from '@sindresorhus/slugify'
|
||||
import arg from 'arg'
|
||||
import commandExists from 'command-exists'
|
||||
|
||||
import type { CliArgs, PackageManager } from './types'
|
||||
|
||||
import { createProject } from './lib/create-project'
|
||||
import { generateSecret } from './lib/generate-secret'
|
||||
import { parseProjectName } from './lib/parse-project-name'
|
||||
import { parseTemplate } from './lib/parse-template'
|
||||
import { selectDb } from './lib/select-db'
|
||||
import { getValidTemplates, validateTemplate } from './lib/templates'
|
||||
import { writeEnvFile } from './lib/write-env-file'
|
||||
import { success } from './utils/log'
|
||||
import { helpMessage, successMessage, welcomeMessage } from './utils/messages'
|
||||
|
||||
export class Main {
|
||||
args: CliArgs
|
||||
|
||||
constructor() {
|
||||
// @ts-expect-error bad typings
|
||||
this.args = arg(
|
||||
{
|
||||
'--db': String,
|
||||
'--help': Boolean,
|
||||
'--name': String,
|
||||
'--secret': String,
|
||||
'--template': String,
|
||||
|
||||
// Package manager
|
||||
'--no-deps': Boolean,
|
||||
'--use-npm': Boolean,
|
||||
'--use-pnpm': Boolean,
|
||||
'--use-yarn': Boolean,
|
||||
|
||||
// Flags
|
||||
'--beta': Boolean,
|
||||
'--dry-run': Boolean,
|
||||
|
||||
// Aliases
|
||||
'-d': '--db',
|
||||
'-h': '--help',
|
||||
'-n': '--name',
|
||||
'-t': '--template',
|
||||
},
|
||||
{ permissive: true },
|
||||
)
|
||||
}
|
||||
|
||||
async init(): Promise<void> {
|
||||
try {
|
||||
if (this.args['--help']) {
|
||||
console.log(helpMessage())
|
||||
process.exit(0)
|
||||
}
|
||||
const templateArg = this.args['--template']
|
||||
if (templateArg) {
|
||||
const valid = validateTemplate(templateArg)
|
||||
if (!valid) {
|
||||
console.log(helpMessage())
|
||||
process.exit(1)
|
||||
}
|
||||
}
|
||||
|
||||
console.log(welcomeMessage)
|
||||
const projectName = await parseProjectName(this.args)
|
||||
const validTemplates = getValidTemplates()
|
||||
const template = await parseTemplate(this.args, validTemplates)
|
||||
|
||||
const projectDir = projectName === '.' ? process.cwd() : `./${slugify(projectName)}`
|
||||
const packageManager = await getPackageManager(this.args)
|
||||
|
||||
if (template.type !== 'plugin') {
|
||||
const dbDetails = await selectDb(this.args, projectName)
|
||||
const payloadSecret = generateSecret()
|
||||
if (!this.args['--dry-run']) {
|
||||
await createProject({
|
||||
cliArgs: this.args,
|
||||
dbDetails,
|
||||
packageManager,
|
||||
projectDir,
|
||||
projectName,
|
||||
template,
|
||||
})
|
||||
await writeEnvFile({
|
||||
databaseUri: dbDetails.dbUri,
|
||||
payloadSecret,
|
||||
projectDir,
|
||||
template,
|
||||
})
|
||||
}
|
||||
} else {
|
||||
if (!this.args['--dry-run']) {
|
||||
await createProject({
|
||||
cliArgs: this.args,
|
||||
packageManager,
|
||||
projectDir,
|
||||
projectName,
|
||||
template,
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
success('Payload project successfully created')
|
||||
console.log(successMessage(projectDir, packageManager))
|
||||
} catch (error: unknown) {
|
||||
console.log(error)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
async function getPackageManager(args: CliArgs): Promise<PackageManager> {
|
||||
let packageManager: PackageManager = 'npm'
|
||||
|
||||
if (args['--use-npm']) {
|
||||
packageManager = 'npm'
|
||||
} else if (args['--use-yarn']) {
|
||||
packageManager = 'yarn'
|
||||
} else if (args['--use-pnpm']) {
|
||||
packageManager = 'pnpm'
|
||||
} else {
|
||||
try {
|
||||
if (await commandExists('yarn')) {
|
||||
packageManager = 'yarn'
|
||||
} else if (await commandExists('pnpm')) {
|
||||
packageManager = 'pnpm'
|
||||
}
|
||||
} catch (error: unknown) {
|
||||
packageManager = 'npm'
|
||||
}
|
||||
}
|
||||
return packageManager
|
||||
}
|
||||
58
packages/create-payload-app/src/types.ts
Normal file
58
packages/create-payload-app/src/types.ts
Normal file
@@ -0,0 +1,58 @@
|
||||
import type arg from 'arg'
|
||||
|
||||
export interface Args extends arg.Spec {
|
||||
'--beta': BooleanConstructor
|
||||
'--db': StringConstructor
|
||||
'--dry-run': BooleanConstructor
|
||||
'--help': BooleanConstructor
|
||||
'--name': StringConstructor
|
||||
'--no-deps': BooleanConstructor
|
||||
'--secret': StringConstructor
|
||||
'--template': StringConstructor
|
||||
'--use-npm': BooleanConstructor
|
||||
'--use-pnpm': BooleanConstructor
|
||||
'--use-yarn': BooleanConstructor
|
||||
'-h': string
|
||||
'-n': string
|
||||
'-t': string
|
||||
}
|
||||
|
||||
export type CliArgs = arg.Result<Args>
|
||||
|
||||
export type ProjectTemplate = GitTemplate | PluginTemplate
|
||||
|
||||
/**
|
||||
* Template that is cloned verbatim from a git repo
|
||||
* Performs .env manipulation based upon input
|
||||
*/
|
||||
export interface GitTemplate extends Template {
|
||||
type: 'starter'
|
||||
url: string
|
||||
}
|
||||
|
||||
/**
|
||||
* Type specifically for the plugin template
|
||||
* No .env manipulation is done
|
||||
*/
|
||||
export interface PluginTemplate extends Template {
|
||||
type: 'plugin'
|
||||
url: string
|
||||
}
|
||||
|
||||
interface Template {
|
||||
description?: string
|
||||
name: string
|
||||
type: ProjectTemplate['type']
|
||||
}
|
||||
|
||||
export type PackageManager = 'npm' | 'pnpm' | 'yarn'
|
||||
|
||||
export type DbType = 'mongodb' | 'postgres'
|
||||
|
||||
export type DbDetails = {
|
||||
dbUri: string
|
||||
type: DbType
|
||||
}
|
||||
|
||||
export type BundlerType = 'vite' | 'webpack'
|
||||
export type EditorType = 'lexical' | 'slate'
|
||||
18
packages/create-payload-app/src/utils/log.ts
Normal file
18
packages/create-payload-app/src/utils/log.ts
Normal file
@@ -0,0 +1,18 @@
|
||||
import chalk from 'chalk'
|
||||
import figures from 'figures'
|
||||
|
||||
export const success = (message: string): void => {
|
||||
console.log(`${chalk.green(figures.tick)} ${chalk.bold(message)}`)
|
||||
}
|
||||
|
||||
export const warning = (message: string): void => {
|
||||
console.log(chalk.yellow('? ') + chalk.bold(message))
|
||||
}
|
||||
|
||||
export const info = (message: string): void => {
|
||||
console.log(`${chalk.yellow(figures.info)} ${chalk.bold(message)}`)
|
||||
}
|
||||
|
||||
export const error = (message: string): void => {
|
||||
console.log(`${chalk.red(figures.cross)} ${chalk.bold(message)}`)
|
||||
}
|
||||
76
packages/create-payload-app/src/utils/messages.ts
Normal file
76
packages/create-payload-app/src/utils/messages.ts
Normal file
@@ -0,0 +1,76 @@
|
||||
import chalk from 'chalk'
|
||||
import figures from 'figures'
|
||||
import path from 'path'
|
||||
import terminalLink from 'terminal-link'
|
||||
|
||||
import type { ProjectTemplate } from '../types'
|
||||
|
||||
import { getValidTemplates } from '../lib/templates'
|
||||
|
||||
const header = (message: string): string => `${chalk.yellow(figures.star)} ${chalk.bold(message)}`
|
||||
|
||||
export const welcomeMessage = chalk`
|
||||
{green Welcome to Payload. Let's create a project! }
|
||||
`
|
||||
|
||||
const spacer = ' '.repeat(8)
|
||||
|
||||
export function helpMessage(): string {
|
||||
const validTemplates = getValidTemplates()
|
||||
return chalk`
|
||||
{bold USAGE}
|
||||
|
||||
{dim $} {bold npx create-payload-app}
|
||||
{dim $} {bold npx create-payload-app} my-project
|
||||
{dim $} {bold npx create-payload-app} -n my-project -t blog
|
||||
|
||||
{bold OPTIONS}
|
||||
|
||||
-n {underline my-payload-app} Set project name
|
||||
-t {underline template_name} Choose specific template
|
||||
|
||||
{dim Available templates: ${formatTemplates(validTemplates)}}
|
||||
|
||||
--use-npm Use npm to install dependencies
|
||||
--use-yarn Use yarn to install dependencies
|
||||
--use-pnpm Use pnpm to install dependencies
|
||||
--no-deps Do not install any dependencies
|
||||
-h Show help
|
||||
`
|
||||
}
|
||||
|
||||
function formatTemplates(templates: ProjectTemplate[]) {
|
||||
return `\n\n${spacer}${templates
|
||||
.map((t) => `${t.name}${' '.repeat(28 - t.name.length)}${t.description}`)
|
||||
.join(`\n${spacer}`)}`
|
||||
}
|
||||
|
||||
export function successMessage(projectDir: string, packageManager: string): string {
|
||||
return `
|
||||
${header('Launch Application:')}
|
||||
|
||||
- cd ${projectDir}
|
||||
- ${
|
||||
packageManager === 'yarn' ? 'yarn' : 'npm run'
|
||||
} dev or follow directions in ${createTerminalLink(
|
||||
'README.md',
|
||||
`file://${path.resolve(projectDir, 'README.md')}`,
|
||||
)}
|
||||
|
||||
${header('Documentation:')}
|
||||
|
||||
- ${createTerminalLink(
|
||||
'Getting Started',
|
||||
'https://payloadcms.com/docs/getting-started/what-is-payload',
|
||||
)}
|
||||
- ${createTerminalLink('Configuration', 'https://payloadcms.com/docs/configuration/overview')}
|
||||
|
||||
`
|
||||
}
|
||||
|
||||
// Create terminalLink with fallback for unsupported terminals
|
||||
function createTerminalLink(text: string, url: string) {
|
||||
return terminalLink(text, url, {
|
||||
fallback: (text, url) => `${text}: ${url}`,
|
||||
})
|
||||
}
|
||||
24
packages/create-payload-app/tsconfig.json
Normal file
24
packages/create-payload-app/tsconfig.json
Normal file
@@ -0,0 +1,24 @@
|
||||
{
|
||||
"extends": "../../tsconfig.json",
|
||||
"compilerOptions": {
|
||||
"composite": true, // Make sure typescript knows that this module depends on their references
|
||||
"noEmit": false /* Do not emit outputs. */,
|
||||
"emitDeclarationOnly": true,
|
||||
"outDir": "./dist" /* Specify an output folder for all emitted files. */,
|
||||
"rootDir": "./src" /* Specify the root folder within your source files. */
|
||||
},
|
||||
"exclude": [
|
||||
"dist",
|
||||
"build",
|
||||
"tests",
|
||||
"test",
|
||||
"node_modules",
|
||||
".eslintrc.js",
|
||||
"src/**/*.spec.js",
|
||||
"src/**/*.spec.jsx",
|
||||
"src/**/*.spec.ts",
|
||||
"src/**/*.spec.tsx"
|
||||
],
|
||||
"include": ["src/**/*.ts", "src/**/*.tsx", "src/**/*.d.ts", "src/**/*.json"],
|
||||
"references": [{ "path": "../payload" }]
|
||||
}
|
||||
@@ -1,6 +1,6 @@
|
||||
{
|
||||
"name": "@payloadcms/db-mongodb",
|
||||
"version": "1.0.2",
|
||||
"version": "1.0.3",
|
||||
"description": "The officially supported MongoDB database adapter for Payload",
|
||||
"repository": "https://github.com/payloadcms/payload",
|
||||
"license": "MIT",
|
||||
@@ -23,7 +23,7 @@
|
||||
"bson-objectid": "2.0.4",
|
||||
"deepmerge": "4.3.1",
|
||||
"get-port": "5.1.1",
|
||||
"mongoose": "6.11.4",
|
||||
"mongoose": "6.12.0",
|
||||
"mongoose-aggregate-paginate-v2": "1.0.6",
|
||||
"mongoose-paginate-v2": "1.7.22",
|
||||
"prompts": "2.4.2",
|
||||
|
||||
@@ -68,7 +68,9 @@ export type MongooseAdapter = BaseDatabaseAdapter &
|
||||
type MongooseAdapterResult = (args: { payload: Payload }) => MongooseAdapter
|
||||
|
||||
declare module 'payload' {
|
||||
export interface DatabaseAdapter extends Args {
|
||||
export interface DatabaseAdapter
|
||||
extends Omit<BaseDatabaseAdapter, 'sessions'>,
|
||||
Omit<Args, 'migrationDir'> {
|
||||
collections: {
|
||||
[slug: string]: CollectionModel
|
||||
}
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
{
|
||||
"name": "@payloadcms/db-postgres",
|
||||
"version": "0.1.1",
|
||||
"version": "0.1.7",
|
||||
"description": "The officially supported Postgres database adapter for Payload",
|
||||
"repository": "https://github.com/payloadcms/payload",
|
||||
"license": "MIT",
|
||||
@@ -34,9 +34,6 @@
|
||||
"@types/to-snake-case": "1.0.0",
|
||||
"payload": "workspace:*"
|
||||
},
|
||||
"peerDependencies": {
|
||||
"better-sqlite3": "^8.5.0"
|
||||
},
|
||||
"publishConfig": {
|
||||
"main": "./dist/index.js",
|
||||
"registry": "https://registry.npmjs.org/",
|
||||
|
||||
@@ -1,6 +1,5 @@
|
||||
import type { Connect } from 'payload/database'
|
||||
|
||||
import { pushSchema } from 'drizzle-kit/utils'
|
||||
import { eq, sql } from 'drizzle-orm'
|
||||
import { drizzle } from 'drizzle-orm/node-postgres'
|
||||
import { numeric, pgTable, timestamp, varchar } from 'drizzle-orm/pg-core'
|
||||
@@ -40,6 +39,8 @@ export const connect: Connect = async function connect(this: PostgresAdapter, pa
|
||||
)
|
||||
return
|
||||
|
||||
const { pushSchema } = require('drizzle-kit/utils')
|
||||
|
||||
// This will prompt if clarifications are needed for Drizzle to push new schema
|
||||
const { apply, hasDataLoss, statementsToExecute, warnings } = await pushSchema(
|
||||
this.schema,
|
||||
|
||||
@@ -2,7 +2,6 @@
|
||||
import type { DrizzleSnapshotJSON } from 'drizzle-kit/utils'
|
||||
import type { CreateMigration } from 'payload/database'
|
||||
|
||||
import { generateDrizzleJson, generateMigration } from 'drizzle-kit/utils'
|
||||
import fs from 'fs'
|
||||
import prompts from 'prompts'
|
||||
|
||||
@@ -61,6 +60,8 @@ export const createMigration: CreateMigration = async function createMigration(
|
||||
fs.mkdirSync(dir)
|
||||
}
|
||||
|
||||
const { generateDrizzleJson, generateMigration } = require('drizzle-kit/utils')
|
||||
|
||||
const [yyymmdd, hhmmss] = new Date().toISOString().split('T')
|
||||
const formattedDate = yyymmdd.replace(/\D/g, '')
|
||||
const formattedTime = hhmmss.split('.')[0].replace(/\D/g, '')
|
||||
|
||||
@@ -150,7 +150,10 @@ export const findMany = async function find({
|
||||
const countResult = await chainMethods({
|
||||
methods: selectCountMethods,
|
||||
query: db
|
||||
.select({ count: sql<number>`count(*)` })
|
||||
.select({
|
||||
count: sql<number>`count
|
||||
(*)`,
|
||||
})
|
||||
.from(table)
|
||||
.where(where),
|
||||
})
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
/* eslint-disable no-param-reassign */
|
||||
import type { Field } from 'payload/types'
|
||||
|
||||
import { fieldAffectsData } from 'payload/types'
|
||||
import { fieldAffectsData, tabHasName } from 'payload/types'
|
||||
import toSnakeCase from 'to-snake-case'
|
||||
|
||||
import type { PostgresAdapter } from '../types'
|
||||
@@ -31,6 +31,42 @@ export const traverseFields = ({
|
||||
topLevelTableName,
|
||||
}: TraverseFieldArgs) => {
|
||||
fields.forEach((field) => {
|
||||
if (field.type === 'collapsible' || field.type === 'row') {
|
||||
traverseFields({
|
||||
_locales,
|
||||
adapter,
|
||||
currentArgs,
|
||||
currentTableName,
|
||||
depth,
|
||||
fields: field.fields,
|
||||
path,
|
||||
topLevelArgs,
|
||||
topLevelTableName,
|
||||
})
|
||||
|
||||
return
|
||||
}
|
||||
|
||||
if (field.type === 'tabs') {
|
||||
field.tabs.forEach((tab) => {
|
||||
const tabPath = tabHasName(tab) ? `${path}${tab.name}_` : path
|
||||
|
||||
traverseFields({
|
||||
_locales,
|
||||
adapter,
|
||||
currentArgs,
|
||||
currentTableName,
|
||||
depth,
|
||||
fields: tab.fields,
|
||||
path: tabPath,
|
||||
topLevelArgs,
|
||||
topLevelTableName,
|
||||
})
|
||||
})
|
||||
|
||||
return
|
||||
}
|
||||
|
||||
if (fieldAffectsData(field)) {
|
||||
switch (field.type) {
|
||||
case 'array': {
|
||||
|
||||
@@ -24,6 +24,7 @@ export const init: Init = async function init(this: PostgresAdapter) {
|
||||
buildTable({
|
||||
adapter: this,
|
||||
buildRelationships: true,
|
||||
disableNotNull: !!collection?.versions?.drafts,
|
||||
disableUnique: false,
|
||||
fields: collection.fields,
|
||||
tableName,
|
||||
@@ -37,6 +38,7 @@ export const init: Init = async function init(this: PostgresAdapter) {
|
||||
buildTable({
|
||||
adapter: this,
|
||||
buildRelationships: true,
|
||||
disableNotNull: !!collection.versions?.drafts,
|
||||
disableUnique: true,
|
||||
fields: versionFields,
|
||||
tableName: versionsTableName,
|
||||
@@ -51,6 +53,7 @@ export const init: Init = async function init(this: PostgresAdapter) {
|
||||
buildTable({
|
||||
adapter: this,
|
||||
buildRelationships: true,
|
||||
disableNotNull: !!global?.versions?.drafts,
|
||||
disableUnique: false,
|
||||
fields: global.fields,
|
||||
tableName,
|
||||
@@ -64,6 +67,7 @@ export const init: Init = async function init(this: PostgresAdapter) {
|
||||
buildTable({
|
||||
adapter: this,
|
||||
buildRelationships: true,
|
||||
disableNotNull: !!global.versions?.drafts,
|
||||
disableUnique: true,
|
||||
fields: versionFields,
|
||||
tableName: versionsTableName,
|
||||
|
||||
@@ -2,9 +2,7 @@
|
||||
import type { Payload } from 'payload'
|
||||
import type { Migration } from 'payload/database'
|
||||
|
||||
import { generateDrizzleJson } from 'drizzle-kit/utils'
|
||||
import { readMigrationFiles } from 'payload/database'
|
||||
import { DatabaseError } from 'pg'
|
||||
import prompts from 'prompts'
|
||||
|
||||
import type { PostgresAdapter } from './types'
|
||||
@@ -78,6 +76,8 @@ export async function migrate(this: PostgresAdapter): Promise<void> {
|
||||
}
|
||||
|
||||
async function runMigrationFile(payload: Payload, migration: Migration, batch: number) {
|
||||
const { generateDrizzleJson } = require('drizzle-kit/utils')
|
||||
|
||||
const start = Date.now()
|
||||
|
||||
payload.logger.info({ msg: `Migrating: ${migration.name}` })
|
||||
|
||||
@@ -34,12 +34,14 @@ type Args = {
|
||||
aliasTable?: GenericTable
|
||||
collectionPath: string
|
||||
columnPrefix?: string
|
||||
constraintPath?: string
|
||||
constraints?: Constraint[]
|
||||
fields: (Field | TabAsField)[]
|
||||
joinAliases: BuildQueryJoinAliases
|
||||
joins: BuildQueryJoins
|
||||
locale?: string
|
||||
pathSegments: string[]
|
||||
rootTableName?: string
|
||||
selectFields: Record<string, GenericColumn>
|
||||
tableName: string
|
||||
}
|
||||
@@ -53,17 +55,22 @@ export const getTableColumnFromPath = ({
|
||||
aliasTable,
|
||||
collectionPath,
|
||||
columnPrefix = '',
|
||||
constraintPath: incomingConstraintPath,
|
||||
constraints = [],
|
||||
fields,
|
||||
joinAliases,
|
||||
joins,
|
||||
locale: incomingLocale,
|
||||
pathSegments: incomingSegments,
|
||||
rootTableName: incomingRootTableName,
|
||||
selectFields,
|
||||
tableName,
|
||||
}: Args): TableColumn => {
|
||||
const fieldPath = incomingSegments[0]
|
||||
let locale = incomingLocale
|
||||
const rootTableName = incomingRootTableName || tableName
|
||||
let constraintPath = incomingConstraintPath || ''
|
||||
|
||||
const field = flattenTopLevelFields(fields as Field[]).find(
|
||||
(fieldToFind) => fieldAffectsData(fieldToFind) && fieldToFind.name === fieldPath,
|
||||
) as Field | TabAsField
|
||||
@@ -105,6 +112,7 @@ export const getTableColumnFromPath = ({
|
||||
aliasTable,
|
||||
collectionPath,
|
||||
columnPrefix,
|
||||
constraintPath,
|
||||
constraints,
|
||||
fields: field.tabs.map((tab) => ({
|
||||
...tab,
|
||||
@@ -114,6 +122,7 @@ export const getTableColumnFromPath = ({
|
||||
joins,
|
||||
locale,
|
||||
pathSegments: pathSegments.slice(1),
|
||||
rootTableName,
|
||||
selectFields,
|
||||
tableName: newTableName,
|
||||
})
|
||||
@@ -125,12 +134,14 @@ export const getTableColumnFromPath = ({
|
||||
aliasTable,
|
||||
collectionPath,
|
||||
columnPrefix: `${columnPrefix}${field.name}_`,
|
||||
constraintPath,
|
||||
constraints,
|
||||
fields: field.fields,
|
||||
joinAliases,
|
||||
joins,
|
||||
locale,
|
||||
pathSegments: pathSegments.slice(1),
|
||||
rootTableName,
|
||||
selectFields,
|
||||
tableName: newTableName,
|
||||
})
|
||||
@@ -140,12 +151,14 @@ export const getTableColumnFromPath = ({
|
||||
aliasTable,
|
||||
collectionPath,
|
||||
columnPrefix,
|
||||
constraintPath,
|
||||
constraints,
|
||||
fields: field.fields,
|
||||
joinAliases,
|
||||
joins,
|
||||
locale,
|
||||
pathSegments: pathSegments.slice(1),
|
||||
rootTableName,
|
||||
selectFields,
|
||||
tableName: newTableName,
|
||||
})
|
||||
@@ -172,12 +185,14 @@ export const getTableColumnFromPath = ({
|
||||
aliasTable,
|
||||
collectionPath,
|
||||
columnPrefix: `${columnPrefix}${field.name}_`,
|
||||
constraintPath,
|
||||
constraints,
|
||||
fields: field.fields,
|
||||
joinAliases,
|
||||
joins,
|
||||
locale,
|
||||
pathSegments: pathSegments.slice(1),
|
||||
rootTableName,
|
||||
selectFields,
|
||||
tableName: newTableName,
|
||||
})
|
||||
@@ -185,6 +200,7 @@ export const getTableColumnFromPath = ({
|
||||
|
||||
case 'array': {
|
||||
newTableName = `${tableName}_${toSnakeCase(field.name)}`
|
||||
constraintPath = `${constraintPath}${field.name}.%.`
|
||||
if (locale && field.localized && adapter.payload.config.localization) {
|
||||
joins[newTableName] = and(
|
||||
eq(adapter.tables[tableName].id, adapter.tables[newTableName]._parentID),
|
||||
@@ -206,12 +222,14 @@ export const getTableColumnFromPath = ({
|
||||
return getTableColumnFromPath({
|
||||
adapter,
|
||||
collectionPath,
|
||||
constraintPath,
|
||||
constraints,
|
||||
fields: field.fields,
|
||||
joinAliases,
|
||||
joins,
|
||||
locale,
|
||||
pathSegments: pathSegments.slice(1),
|
||||
rootTableName,
|
||||
selectFields,
|
||||
tableName: newTableName,
|
||||
})
|
||||
@@ -229,12 +247,14 @@ export const getTableColumnFromPath = ({
|
||||
result = getTableColumnFromPath({
|
||||
adapter,
|
||||
collectionPath,
|
||||
constraintPath: '',
|
||||
constraints: blockConstraints,
|
||||
fields: block.fields,
|
||||
joinAliases,
|
||||
joins,
|
||||
locale,
|
||||
pathSegments: pathSegments.slice(1),
|
||||
rootTableName,
|
||||
selectFields: blockSelectFields,
|
||||
tableName: newTableName,
|
||||
})
|
||||
@@ -283,9 +303,8 @@ export const getTableColumnFromPath = ({
|
||||
case 'relationship':
|
||||
case 'upload': {
|
||||
let relationshipFields
|
||||
const relationTableName = `${tableName}_rels`
|
||||
const relationTableName = `${rootTableName}_rels`
|
||||
const newCollectionPath = pathSegments.slice(1).join('.')
|
||||
|
||||
const aliasRelationshipTableName = uuid()
|
||||
const aliasRelationshipTable = alias(
|
||||
adapter.tables[relationTableName],
|
||||
@@ -295,7 +314,7 @@ export const getTableColumnFromPath = ({
|
||||
// Join in the relationships table
|
||||
joinAliases.push({
|
||||
condition: eq(
|
||||
(aliasTable || adapter.tables[tableName]).id,
|
||||
(aliasTable || adapter.tables[rootTableName]).id,
|
||||
aliasRelationshipTable.parent,
|
||||
),
|
||||
table: aliasRelationshipTable,
|
||||
@@ -306,7 +325,7 @@ export const getTableColumnFromPath = ({
|
||||
constraints.push({
|
||||
columnName: 'path',
|
||||
table: aliasRelationshipTable,
|
||||
value: field.name,
|
||||
value: `${constraintPath}${field.name}`,
|
||||
})
|
||||
|
||||
let newAliasTable
|
||||
@@ -368,6 +387,7 @@ export const getTableColumnFromPath = ({
|
||||
joins,
|
||||
locale,
|
||||
pathSegments: pathSegments.slice(1),
|
||||
rootTableName: newTableName,
|
||||
selectFields,
|
||||
tableName: newTableName,
|
||||
})
|
||||
|
||||
@@ -2,7 +2,7 @@
|
||||
import type { SQL } from 'drizzle-orm'
|
||||
import type { Field, Operator, Where } from 'payload/types'
|
||||
|
||||
import { and, ilike, isNotNull, isNull, ne, or, sql } from 'drizzle-orm'
|
||||
import { and, ilike, isNotNull, isNull, ne, notInArray, or, sql } from 'drizzle-orm'
|
||||
import { QueryError } from 'payload/errors'
|
||||
import { validOperators } from 'payload/types'
|
||||
|
||||
@@ -100,7 +100,11 @@ export async function parseParams({
|
||||
const val = where[relationOrPath][operator]
|
||||
|
||||
queryConstraints.forEach(({ columnName: col, table: constraintTable, value }) => {
|
||||
constraints.push(operatorMap.equals(constraintTable[col], value))
|
||||
if (typeof value === 'string' && value.indexOf('%') > -1) {
|
||||
constraints.push(operatorMap.like(constraintTable[col], value))
|
||||
} else {
|
||||
constraints.push(operatorMap.equals(constraintTable[col], value))
|
||||
}
|
||||
})
|
||||
|
||||
if (['json', 'richText'].includes(field.type) && Array.isArray(pathSegments)) {
|
||||
@@ -147,6 +151,7 @@ export async function parseParams({
|
||||
const { operator: queryOperator, value: queryValue } = sanitizeQueryValue({
|
||||
field,
|
||||
operator,
|
||||
relationOrPath,
|
||||
val,
|
||||
})
|
||||
|
||||
@@ -158,6 +163,17 @@ export async function parseParams({
|
||||
ne<any>(rawColumn || table[columnName], queryValue),
|
||||
),
|
||||
)
|
||||
} else if (
|
||||
(field.type === 'relationship' || field.type === 'upload') &&
|
||||
Array.isArray(queryValue) &&
|
||||
operator === 'not_in'
|
||||
) {
|
||||
constraints.push(
|
||||
sql`${notInArray(table[columnName], queryValue)} OR
|
||||
${table[columnName]}
|
||||
IS
|
||||
NULL`,
|
||||
)
|
||||
} else {
|
||||
constraints.push(
|
||||
operatorMap[queryOperator](rawColumn || table[columnName], queryValue),
|
||||
|
||||
@@ -5,12 +5,14 @@ import { createArrayFromCommaDelineated } from 'payload/utilities'
|
||||
type SanitizeQueryValueArgs = {
|
||||
field: Field | TabAsField
|
||||
operator: string
|
||||
relationOrPath: string
|
||||
val: any
|
||||
}
|
||||
|
||||
export const sanitizeQueryValue = ({
|
||||
field,
|
||||
operator: operatorArg,
|
||||
relationOrPath,
|
||||
val,
|
||||
}: SanitizeQueryValueArgs): { operator: string; value: unknown } => {
|
||||
let operator = operatorArg
|
||||
@@ -18,6 +20,22 @@ export const sanitizeQueryValue = ({
|
||||
|
||||
if (!fieldAffectsData(field)) return { operator, value: formattedValue }
|
||||
|
||||
if (
|
||||
(field.type === 'relationship' || field.type === 'upload') &&
|
||||
!relationOrPath.endsWith('relationTo') &&
|
||||
Array.isArray(formattedValue)
|
||||
) {
|
||||
const allPossibleIDTypes: (number | string)[] = []
|
||||
formattedValue.forEach((val) => {
|
||||
if (typeof val === 'string') {
|
||||
allPossibleIDTypes.push(val, parseInt(val))
|
||||
} else {
|
||||
allPossibleIDTypes.push(val, String(val))
|
||||
}
|
||||
})
|
||||
formattedValue = allPossibleIDTypes
|
||||
}
|
||||
|
||||
// Cast incoming values as proper searchable types
|
||||
if (field.type === 'checkbox' && typeof val === 'string') {
|
||||
if (val.toLowerCase() === 'true') formattedValue = true
|
||||
|
||||
@@ -6,11 +6,12 @@
|
||||
|
||||
// drizzle-kit@utils
|
||||
|
||||
import { generateDrizzleJson, generateMigration, pushSchema } from 'drizzle-kit/utils'
|
||||
import { drizzle } from 'drizzle-orm/node-postgres'
|
||||
import { Pool } from 'pg'
|
||||
|
||||
async function generateUsage() {
|
||||
const { generateDrizzleJson, generateMigration } = require('drizzle-kit/utils')
|
||||
|
||||
// @ts-expect-error Just TypeScript being broken // TODO: Open TypeScript issue
|
||||
const schema = await import('./data/users')
|
||||
// @ts-expect-error Just TypeScript being broken // TODO: Open TypeScript issue
|
||||
@@ -25,6 +26,8 @@ async function generateUsage() {
|
||||
}
|
||||
|
||||
async function pushUsage() {
|
||||
const { pushSchema } = require('drizzle-kit/utils')
|
||||
|
||||
// @ts-expect-error Just TypeScript being broken // TODO: Open TypeScript issue
|
||||
const schemaAfter = await import('./data/users-after')
|
||||
|
||||
|
||||
@@ -27,6 +27,7 @@ type Args = {
|
||||
baseColumns?: Record<string, PgColumnBuilder>
|
||||
baseExtraConfig?: Record<string, (cols: GenericColumns) => IndexBuilder | UniqueConstraintBuilder>
|
||||
buildRelationships?: boolean
|
||||
disableNotNull: boolean
|
||||
disableUnique: boolean
|
||||
fields: Field[]
|
||||
rootRelationsToBuild?: Map<string, string>
|
||||
@@ -46,6 +47,7 @@ export const buildTable = ({
|
||||
baseColumns = {},
|
||||
baseExtraConfig = {},
|
||||
buildRelationships,
|
||||
disableNotNull,
|
||||
disableUnique = false,
|
||||
fields,
|
||||
rootRelationsToBuild,
|
||||
@@ -102,6 +104,7 @@ export const buildTable = ({
|
||||
adapter,
|
||||
buildRelationships,
|
||||
columns,
|
||||
disableNotNull,
|
||||
disableUnique,
|
||||
fields,
|
||||
indexes,
|
||||
|
||||
@@ -34,6 +34,7 @@ type Args = {
|
||||
buildRelationships: boolean
|
||||
columnPrefix?: string
|
||||
columns: Record<string, PgColumnBuilder>
|
||||
disableNotNull: boolean
|
||||
disableUnique?: boolean
|
||||
fieldPrefix?: string
|
||||
fields: (Field | TabAsField)[]
|
||||
@@ -62,6 +63,7 @@ export const traverseFields = ({
|
||||
buildRelationships,
|
||||
columnPrefix,
|
||||
columns,
|
||||
disableNotNull,
|
||||
disableUnique = false,
|
||||
fieldPrefix,
|
||||
fields,
|
||||
@@ -174,7 +176,7 @@ export const traverseFields = ({
|
||||
|
||||
case 'radio':
|
||||
case 'select': {
|
||||
const enumName = `enum_${newTableName}_${columnPrefix || ''}${toSnakeCase(field.name)}`
|
||||
const enumName = `enum_${newTableName}_${toSnakeCase(field.name)}`
|
||||
|
||||
adapter.enums[enumName] = pgEnum(
|
||||
enumName,
|
||||
@@ -188,7 +190,7 @@ export const traverseFields = ({
|
||||
)
|
||||
|
||||
if (field.type === 'select' && field.hasMany) {
|
||||
const selectTableName = `${newTableName}_${toSnakeCase(fieldName)}`
|
||||
const selectTableName = `${newTableName}_${toSnakeCase(field.name)}`
|
||||
const baseColumns: Record<string, PgColumnBuilder> = {
|
||||
order: integer('order').notNull(),
|
||||
parent: parentIDColumnMap[parentIDColType]('parent_id')
|
||||
@@ -218,6 +220,7 @@ export const traverseFields = ({
|
||||
adapter,
|
||||
baseColumns,
|
||||
baseExtraConfig,
|
||||
disableNotNull,
|
||||
disableUnique,
|
||||
fields: [],
|
||||
tableName: selectTableName,
|
||||
@@ -249,6 +252,8 @@ export const traverseFields = ({
|
||||
}
|
||||
|
||||
case 'array': {
|
||||
const disableNotNullFromHere = Boolean(field.admin?.condition) || disableNotNull
|
||||
|
||||
const arrayTableName = `${newTableName}_${toSnakeCase(field.name)}`
|
||||
const baseColumns: Record<string, PgColumnBuilder> = {
|
||||
_order: integer('_order').notNull(),
|
||||
@@ -274,6 +279,7 @@ export const traverseFields = ({
|
||||
adapter,
|
||||
baseColumns,
|
||||
baseExtraConfig,
|
||||
disableNotNull: disableNotNullFromHere,
|
||||
disableUnique,
|
||||
fields: field.fields,
|
||||
rootRelationsToBuild,
|
||||
@@ -310,6 +316,8 @@ export const traverseFields = ({
|
||||
}
|
||||
|
||||
case 'blocks': {
|
||||
const disableNotNullFromHere = Boolean(field.admin?.condition) || disableNotNull
|
||||
|
||||
field.blocks.forEach((block) => {
|
||||
const blockTableName = `${rootTableName}_blocks_${toSnakeCase(block.slug)}`
|
||||
if (!adapter.tables[blockTableName]) {
|
||||
@@ -339,6 +347,7 @@ export const traverseFields = ({
|
||||
adapter,
|
||||
baseColumns,
|
||||
baseExtraConfig,
|
||||
disableNotNull: disableNotNullFromHere,
|
||||
disableUnique,
|
||||
fields: block.fields,
|
||||
rootRelationsToBuild,
|
||||
@@ -399,6 +408,7 @@ export const traverseFields = ({
|
||||
buildRelationships,
|
||||
columnPrefix,
|
||||
columns,
|
||||
disableNotNull,
|
||||
disableUnique,
|
||||
fieldPrefix,
|
||||
fields: field.fields,
|
||||
@@ -406,7 +416,7 @@ export const traverseFields = ({
|
||||
indexes,
|
||||
localesColumns,
|
||||
localesIndexes,
|
||||
newTableName: parentTableName,
|
||||
newTableName,
|
||||
parentTableName,
|
||||
relationsToBuild,
|
||||
relationships,
|
||||
@@ -422,6 +432,8 @@ export const traverseFields = ({
|
||||
break
|
||||
}
|
||||
|
||||
const disableNotNullFromHere = Boolean(field.admin?.condition) || disableNotNull
|
||||
|
||||
const {
|
||||
hasLocalizedField: groupHasLocalizedField,
|
||||
hasLocalizedManyNumberField: groupHasLocalizedManyNumberField,
|
||||
@@ -432,6 +444,7 @@ export const traverseFields = ({
|
||||
buildRelationships,
|
||||
columnPrefix: `${columnName}_`,
|
||||
columns,
|
||||
disableNotNull: disableNotNullFromHere,
|
||||
disableUnique,
|
||||
fieldPrefix: `${fieldName}_`,
|
||||
fields: field.fields,
|
||||
@@ -456,6 +469,8 @@ export const traverseFields = ({
|
||||
}
|
||||
|
||||
case 'tabs': {
|
||||
const disableNotNullFromHere = Boolean(field.admin?.condition) || disableNotNull
|
||||
|
||||
const {
|
||||
hasLocalizedField: tabHasLocalizedField,
|
||||
hasLocalizedManyNumberField: tabHasLocalizedManyNumberField,
|
||||
@@ -466,6 +481,7 @@ export const traverseFields = ({
|
||||
buildRelationships,
|
||||
columnPrefix,
|
||||
columns,
|
||||
disableNotNull: disableNotNullFromHere,
|
||||
disableUnique,
|
||||
fieldPrefix,
|
||||
fields: field.tabs.map((tab) => ({ ...tab, type: 'tab' })),
|
||||
@@ -492,6 +508,7 @@ export const traverseFields = ({
|
||||
|
||||
case 'row':
|
||||
case 'collapsible': {
|
||||
const disableNotNullFromHere = Boolean(field.admin?.condition) || disableNotNull
|
||||
const {
|
||||
hasLocalizedField: rowHasLocalizedField,
|
||||
hasLocalizedManyNumberField: rowHasLocalizedManyNumberField,
|
||||
@@ -502,6 +519,7 @@ export const traverseFields = ({
|
||||
buildRelationships,
|
||||
columnPrefix,
|
||||
columns,
|
||||
disableNotNull: disableNotNullFromHere,
|
||||
disableUnique,
|
||||
fieldPrefix,
|
||||
fields: field.fields,
|
||||
@@ -544,7 +562,13 @@ export const traverseFields = ({
|
||||
|
||||
const condition = field.admin && field.admin.condition
|
||||
|
||||
if (targetTable[fieldName] && 'required' in field && field.required && !condition) {
|
||||
if (
|
||||
!disableNotNull &&
|
||||
targetTable[fieldName] &&
|
||||
'required' in field &&
|
||||
field.required &&
|
||||
!condition
|
||||
) {
|
||||
targetTable[fieldName].notNull()
|
||||
}
|
||||
})
|
||||
|
||||
@@ -69,7 +69,9 @@ export type MigrateUpArgs = { payload: Payload }
|
||||
export type MigrateDownArgs = { payload: Payload }
|
||||
|
||||
declare module 'payload' {
|
||||
export interface DatabaseAdapter extends Omit<Args, 'pool'> {
|
||||
export interface DatabaseAdapter
|
||||
extends Omit<Args, 'migrationDir' | 'pool'>,
|
||||
BaseDatabaseAdapter {
|
||||
drizzle: DrizzleDB
|
||||
enums: Record<string, GenericEnum>
|
||||
pool: Pool
|
||||
|
||||
@@ -3,6 +3,6 @@ module.exports = {
|
||||
jest: true,
|
||||
},
|
||||
plugins: ['jest', 'jest-dom'],
|
||||
extends: ['./rules/jest.cjs', './rules/jest-dom.cjs'].map(require.resolve),
|
||||
extends: ['./rules/jest.js', './rules/jest-dom.js'].map(require.resolve),
|
||||
rules: {},
|
||||
}
|
||||
@@ -13,6 +13,6 @@ module.exports = {
|
||||
jsx: true,
|
||||
},
|
||||
},
|
||||
extends: ['./rules/react-a11y.cjs', './rules/react.cjs'].map(require.resolve),
|
||||
extends: ['./rules/react-a11y.js', './rules/react.js'].map(require.resolve),
|
||||
rules: {},
|
||||
}
|
||||
@@ -11,8 +11,8 @@ module.exports = {
|
||||
'plugin:regexp/recommended',
|
||||
'plugin:react/recommended',
|
||||
'plugin:react-hooks/recommended',
|
||||
'./configs/jest/index.cjs',
|
||||
'./configs/react/index.cjs',
|
||||
'./configs/jest/index.js',
|
||||
'./configs/react/index.js',
|
||||
'prettier',
|
||||
],
|
||||
parser: '@typescript-eslint/parser',
|
||||
@@ -1,4 +1,4 @@
|
||||
module.exports = {
|
||||
root: true,
|
||||
extends: ['./eslint-config/index.cjs'],
|
||||
extends: ['./eslint-config/index.js'],
|
||||
}
|
||||
|
||||
@@ -1,7 +1,6 @@
|
||||
{
|
||||
"name": "@payloadcms/eslint-config",
|
||||
"version": "0.0.1",
|
||||
"private": true,
|
||||
"description": "Payload styles for ESLint and Prettier",
|
||||
"license": "MIT",
|
||||
"author": {
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
{
|
||||
"name": "@payloadcms/live-preview-react",
|
||||
"version": "0.1.1",
|
||||
"version": "0.1.3",
|
||||
"description": "The official live preview React SDK for Payload",
|
||||
"repository": "https://github.com/payloadcms/payload",
|
||||
"license": "MIT",
|
||||
@@ -17,15 +17,16 @@
|
||||
"prepublishOnly": "pnpm clean && pnpm build"
|
||||
},
|
||||
"dependencies": {
|
||||
"react": "18.2.0",
|
||||
"@payloadcms/live-preview": "workspace:*"
|
||||
},
|
||||
"devDependencies": {
|
||||
"@payloadcms/eslint-config": "workspace:*",
|
||||
"@types/node": "20.5.7",
|
||||
"@types/react": "18.2.15",
|
||||
"@types/react": "^18.2.0",
|
||||
"payload": "workspace:*"
|
||||
},
|
||||
"peerDependencies": {
|
||||
"react": "^16.8.0 || ^17.0.0 || ^18.0.0"
|
||||
},
|
||||
"exports": {
|
||||
".": {
|
||||
"default": "./src/index.ts",
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
import { subscribe, unsubscribe } from '@payloadcms/live-preview'
|
||||
import { useCallback, useEffect, useState } from 'react'
|
||||
import { ready, subscribe, unsubscribe } from '@payloadcms/live-preview'
|
||||
import { useCallback, useEffect, useRef, useState } from 'react'
|
||||
|
||||
// To prevent the flicker of missing data on initial load,
|
||||
// you can pass in the initial page data from the server
|
||||
@@ -17,6 +17,7 @@ export const useLivePreview = <T extends any>(props: {
|
||||
const { depth = 0, initialData, serverURL } = props
|
||||
const [data, setData] = useState<T>(initialData)
|
||||
const [isLoading, setIsLoading] = useState<boolean>(true)
|
||||
const hasSentReadyMessage = useRef<boolean>(false)
|
||||
|
||||
const onChange = useCallback((mergedData) => {
|
||||
setData(mergedData)
|
||||
@@ -31,6 +32,14 @@ export const useLivePreview = <T extends any>(props: {
|
||||
serverURL,
|
||||
})
|
||||
|
||||
if (!hasSentReadyMessage.current) {
|
||||
hasSentReadyMessage.current = true
|
||||
|
||||
ready({
|
||||
serverURL,
|
||||
})
|
||||
}
|
||||
|
||||
return () => {
|
||||
unsubscribe(subscription)
|
||||
}
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
{
|
||||
"name": "@payloadcms/live-preview",
|
||||
"version": "0.1.1",
|
||||
"version": "0.1.3",
|
||||
"description": "The official live preview JavaScript SDK for Payload",
|
||||
"repository": "https://github.com/payloadcms/payload",
|
||||
"license": "MIT",
|
||||
@@ -16,13 +16,8 @@
|
||||
"copyfiles": "copyfiles -u 1 \"src/**/*.{html,css,scss,ttf,woff,woff2,eot,svg,jpg,png,json}\" dist/",
|
||||
"prepublishOnly": "pnpm clean && pnpm build"
|
||||
},
|
||||
"dependencies": {
|
||||
"react": "18.2.0"
|
||||
},
|
||||
"devDependencies": {
|
||||
"@payloadcms/eslint-config": "workspace:*",
|
||||
"@types/node": "20.5.7",
|
||||
"@types/react": "18.2.15",
|
||||
"payload": "workspace:*"
|
||||
},
|
||||
"exports": {
|
||||
|
||||
@@ -7,11 +7,10 @@ export const handleMessage = async <T>(args: {
|
||||
serverURL: string
|
||||
}): Promise<T> => {
|
||||
const { depth, event, initialData, serverURL } = args
|
||||
|
||||
if (event.origin === serverURL && event.data) {
|
||||
const eventData = JSON.parse(event?.data)
|
||||
|
||||
if (eventData.type === 'livePreview') {
|
||||
if (eventData.type === 'payload-live-preview') {
|
||||
const mergedData = await mergeData<T>({
|
||||
depth,
|
||||
fieldSchema: eventData.fieldSchemaJSON,
|
||||
|
||||
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user