Compare commits
666 Commits
alpha-post
...
fix/disabl
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
3a0eb0e710 | ||
|
|
b156a8beb7 | ||
|
|
d074f3f407 | ||
|
|
6f6c1435c7 | ||
|
|
332b8b6f34 | ||
|
|
94f1dfef52 | ||
|
|
71f19fba58 | ||
|
|
24b18fb0fd | ||
|
|
5731241a5c | ||
|
|
47e70abb4e | ||
|
|
0ede95f375 | ||
|
|
b723efdd3b | ||
|
|
14c513690d | ||
|
|
88f239e784 | ||
|
|
a1f6bf8a67 | ||
|
|
912dcd38df | ||
|
|
da5028cdee | ||
|
|
899faa62f1 | ||
|
|
9df6a644c9 | ||
|
|
1a6d9eaa11 | ||
|
|
7d447af277 | ||
|
|
d8baaab849 | ||
|
|
3e1523f007 | ||
|
|
fa38af025f | ||
|
|
6ca9ff847f | ||
|
|
a8824b2b51 | ||
|
|
6aa3752b16 | ||
|
|
c483a439bf | ||
|
|
74bdf1c681 | ||
|
|
7437d9fe58 | ||
|
|
51f7351962 | ||
|
|
d01fcb921b | ||
|
|
6179c938bf | ||
|
|
dbbcb658a9 | ||
|
|
16f97ad7c3 | ||
|
|
bc7445ed99 | ||
|
|
e4d024cd0d | ||
|
|
1005de8295 | ||
|
|
c79289cedf | ||
|
|
6a745be036 | ||
|
|
cee9cc33ed | ||
|
|
9a5e9313cd | ||
|
|
5401af5812 | ||
|
|
6305a1d1c2 | ||
|
|
95b96e3e9e | ||
|
|
95b3f6d40d | ||
|
|
c258a4bef1 | ||
|
|
647544a0c6 | ||
|
|
7e0a2a879c | ||
|
|
471e1388ae | ||
|
|
1da430b042 | ||
|
|
56ac06c563 | ||
|
|
4dec4bb61c | ||
|
|
99a09c49a3 | ||
|
|
88fd46bfea | ||
|
|
8a6603b3d8 | ||
|
|
f6c9f454a5 | ||
|
|
d8a5426c37 | ||
|
|
c9011dcbfd | ||
|
|
43089fd13c | ||
|
|
bb3bd9c395 | ||
|
|
ba423ab424 | ||
|
|
c23984cac3 | ||
|
|
6685a0fa7e | ||
|
|
ac4750d016 | ||
|
|
951e9fd7f2 | ||
|
|
cbd1554589 | ||
|
|
80c545933f | ||
|
|
594f319fc6 | ||
|
|
102feb9576 | ||
|
|
68274d2862 | ||
|
|
8945b7a4fa | ||
|
|
d5ef93b2ba | ||
|
|
cb0f0dba3a | ||
|
|
7b263be01b | ||
|
|
56df60f520 | ||
|
|
d5cbbc472d | ||
|
|
d987e5628a | ||
|
|
1383191f15 | ||
|
|
27297284cf | ||
|
|
3af3a91c87 | ||
|
|
23c5b71f95 | ||
|
|
2ee6a8ec3a | ||
|
|
10819b8693 | ||
|
|
83c617b452 | ||
|
|
4acb133655 | ||
|
|
6e4135e790 | ||
|
|
f0198b62f3 | ||
|
|
3ff8063ab8 | ||
|
|
8d52f1b279 | ||
|
|
24072d222c | ||
|
|
55c59e71da | ||
|
|
62233788e0 | ||
|
|
b297c5499d | ||
|
|
fb7925f272 | ||
|
|
221e873862 | ||
|
|
e7143e02e2 | ||
|
|
a1d68bd951 | ||
|
|
93ee452a2d | ||
|
|
1abaa5fc17 | ||
|
|
999059bc61 | ||
|
|
39ba39c237 | ||
|
|
009e6c2066 | ||
|
|
8bf03ae706 | ||
|
|
a2fe3f66e3 | ||
|
|
6cd5b253f1 | ||
|
|
abf0461d80 | ||
|
|
abca45e152 | ||
|
|
58ea94f6ac | ||
|
|
49cba92fa1 | ||
|
|
42329fc736 | ||
|
|
68dee49501 | ||
|
|
234837ee1d | ||
|
|
0d3554d70a | ||
|
|
7f6c6c4787 | ||
|
|
b6c975bfdc | ||
|
|
eaf5a86121 | ||
|
|
b9a9dad60a | ||
|
|
a2afc38894 | ||
|
|
b80c92ba93 | ||
|
|
6669a2cedb | ||
|
|
7369da3d8d | ||
|
|
697a0f1ecf | ||
|
|
3db0557b07 | ||
|
|
8178d57ab9 | ||
|
|
f1b2f767bb | ||
|
|
f21b394d21 | ||
|
|
4e4ccca02a | ||
|
|
b6578d6447 | ||
|
|
abeb94a53d | ||
|
|
974a74500b | ||
|
|
61dd17ae5e | ||
|
|
2628249a51 | ||
|
|
5f57782199 | ||
|
|
bceb49ee6c | ||
|
|
beeb59f263 | ||
|
|
4150c87be0 | ||
|
|
a394d8211e | ||
|
|
6a162776f2 | ||
|
|
d41bd7b133 | ||
|
|
f3409fab29 | ||
|
|
dd75fbfee2 | ||
|
|
ae2c85f947 | ||
|
|
c0b454a5de | ||
|
|
27754dd0d7 | ||
|
|
18ec830882 | ||
|
|
1ffc0f552e | ||
|
|
07b676ac81 | ||
|
|
da79f09544 | ||
|
|
993b035285 | ||
|
|
3f2df643e7 | ||
|
|
42c7649176 | ||
|
|
2722d2f5ce | ||
|
|
f71e61d7d9 | ||
|
|
73c76cab77 | ||
|
|
43e8a533b7 | ||
|
|
ea4203bb32 | ||
|
|
568b5073c8 | ||
|
|
471e1f4827 | ||
|
|
b9185a6fcd | ||
|
|
80496aa94c | ||
|
|
5fd6e3c1a8 | ||
|
|
54590c1700 | ||
|
|
b1259be8f2 | ||
|
|
cd161e4b16 | ||
|
|
cb4214fe6e | ||
|
|
9d42751a42 | ||
|
|
c2c637b359 | ||
|
|
2f446e11d6 | ||
|
|
4f566b088c | ||
|
|
0d40d87b31 | ||
|
|
70fcd6bf40 | ||
|
|
94c0095b3b | ||
|
|
4328060637 | ||
|
|
d98d0fd5bd | ||
|
|
5db2863d08 | ||
|
|
ff5e438d6d | ||
|
|
bfd5f13ee9 | ||
|
|
8043188f36 | ||
|
|
e3e0998772 | ||
|
|
86adc6f282 | ||
|
|
b51b519d30 | ||
|
|
c70dcb6a59 | ||
|
|
2486c7dba0 | ||
|
|
1456fcdcad | ||
|
|
a216800c72 | ||
|
|
c3d8597c13 | ||
|
|
844663ce1a | ||
|
|
055e6af7b7 | ||
|
|
479e6ecddc | ||
|
|
b9456e8244 | ||
|
|
432dfef435 | ||
|
|
429c6f7a48 | ||
|
|
6d41f6c56d | ||
|
|
20ac2b86cf | ||
|
|
b88455166a | ||
|
|
9b86de1f9d | ||
|
|
1393c72281 | ||
|
|
bcb538aee2 | ||
|
|
01e8f8c649 | ||
|
|
1119cf3af9 | ||
|
|
216934145c | ||
|
|
40f952cac3 | ||
|
|
330e4a7724 | ||
|
|
e676503e02 | ||
|
|
06233fbb2f | ||
|
|
17298695b1 | ||
|
|
c1081ccfe2 | ||
|
|
30da5a8643 | ||
|
|
55bf5436e4 | ||
|
|
a4956dc649 | ||
|
|
512b7bd429 | ||
|
|
5119c51439 | ||
|
|
f3e25f3277 | ||
|
|
1275c70187 | ||
|
|
be69fc448d | ||
|
|
bcccefe98e | ||
|
|
2061f38d9e | ||
|
|
d0869d9087 | ||
|
|
1eabf316d6 | ||
|
|
a0dd750a52 | ||
|
|
2fc9885abc | ||
|
|
14d683fb9a | ||
|
|
e286519cb1 | ||
|
|
b1e78a3562 | ||
|
|
0bc103658a | ||
|
|
9037b9b4fa | ||
|
|
d194493e9a | ||
|
|
aa22344cdb | ||
|
|
736e7b822e | ||
|
|
2ebda95036 | ||
|
|
d8783eaad4 | ||
|
|
cddb08de1a | ||
|
|
d4e5d3df54 | ||
|
|
414b03ce74 | ||
|
|
96dbab8834 | ||
|
|
03a110a750 | ||
|
|
6accc705be | ||
|
|
312dca003b | ||
|
|
4f9fdb6c14 | ||
|
|
94af06466b | ||
|
|
7cf2686097 | ||
|
|
f14883aa11 | ||
|
|
9df8de2386 | ||
|
|
8b2cf4705e | ||
|
|
364e9832ac | ||
|
|
2deeb61f17 | ||
|
|
14498e8a9c | ||
|
|
eb78022387 | ||
|
|
3677a59a78 | ||
|
|
7c1c840a59 | ||
|
|
a73eaf5d37 | ||
|
|
68989a58a8 | ||
|
|
9841731ae7 | ||
|
|
ba7ac5d439 | ||
|
|
7c60772b26 | ||
|
|
1141a5d3af | ||
|
|
6f74fd1f98 | ||
|
|
75873bfcfa | ||
|
|
1faf621f17 | ||
|
|
1d1c73dfcc | ||
|
|
d057ce0a85 | ||
|
|
0ce26d2c08 | ||
|
|
abf285d713 | ||
|
|
c2ee8e3999 | ||
|
|
167ba0c68f | ||
|
|
9ad1cbe920 | ||
|
|
313ea52e3d | ||
|
|
3acfb7a83f | ||
|
|
783dae2bbb | ||
|
|
59681b211b | ||
|
|
98438175cf | ||
|
|
af40302e5f | ||
|
|
ec0e0ae449 | ||
|
|
607ff17033 | ||
|
|
e73e610669 | ||
|
|
30fddde066 | ||
|
|
a56d2842fb | ||
|
|
35f59a47cc | ||
|
|
817d57bd12 | ||
|
|
5826048e7b | ||
|
|
1a975b31cf | ||
|
|
73298a80f0 | ||
|
|
a5d14ef4c1 | ||
|
|
d0c79b65f8 | ||
|
|
2fc50b1a1f | ||
|
|
0ddeedb0b3 | ||
|
|
09c2fb10f3 | ||
|
|
5bfff5b7ba | ||
|
|
702088375c | ||
|
|
ea507fbcc4 | ||
|
|
0ff1e6632b | ||
|
|
996ee47f96 | ||
|
|
3e9bd5bb62 | ||
|
|
ee7221c986 | ||
|
|
318c126ae3 | ||
|
|
2154aea89f | ||
|
|
4d3ad1af35 | ||
|
|
75cab7688f | ||
|
|
5084d6dd97 | ||
|
|
518f80cbb6 | ||
|
|
c9399efa65 | ||
|
|
dd9133659c | ||
|
|
d3016b7eb5 | ||
|
|
69e884f5b7 | ||
|
|
6d122905f4 | ||
|
|
e6e016ac2d | ||
|
|
3e7925e33f | ||
|
|
7a2ccba63c | ||
|
|
be2134eb69 | ||
|
|
95e422b0e1 | ||
|
|
53d9c4ca95 | ||
|
|
30948ab545 | ||
|
|
906df6b401 | ||
|
|
b9c585bab5 | ||
|
|
12203140ad | ||
|
|
0704152e38 | ||
|
|
f582efe98d | ||
|
|
540579f520 | ||
|
|
24c348dc49 | ||
|
|
4b4c245507 | ||
|
|
400f68d1aa | ||
|
|
5bb27ed9cd | ||
|
|
833498c269 | ||
|
|
80507d487b | ||
|
|
08f4ebaaf8 | ||
|
|
4c418525eb | ||
|
|
4962f6c926 | ||
|
|
50f0e9298c | ||
|
|
89efcc5db1 | ||
|
|
c3119a5632 | ||
|
|
069bbd92b0 | ||
|
|
c4422a2593 | ||
|
|
3aab9d368e | ||
|
|
b6afab63b2 | ||
|
|
b93f5e9c44 | ||
|
|
630082035f | ||
|
|
c74e41fc76 | ||
|
|
08ce7c58b5 | ||
|
|
1853fde379 | ||
|
|
f29d22ca95 | ||
|
|
5ea5f928ab | ||
|
|
efd6d35eb3 | ||
|
|
2b2538f13a | ||
|
|
9375dae179 | ||
|
|
674bb3758d | ||
|
|
cedf9a2eb8 | ||
|
|
7d2dc5b6c6 | ||
|
|
9d2aad7bf9 | ||
|
|
f085d7609b | ||
|
|
a49243a42a | ||
|
|
e79f431f14 | ||
|
|
38e5b6e8e3 | ||
|
|
35bdb785c4 | ||
|
|
25cb146fde | ||
|
|
c10f0f4a9e | ||
|
|
3a3a7f6e16 | ||
|
|
1d3b500962 | ||
|
|
f39f95af6d | ||
|
|
6fec2bbe1c | ||
|
|
2412134073 | ||
|
|
136545d1fd | ||
|
|
684c4d2113 | ||
|
|
6624fd0401 | ||
|
|
31502d2da3 | ||
|
|
3ee39ecca3 | ||
|
|
89e7c305e7 | ||
|
|
60dd71c59e | ||
|
|
0936f77930 | ||
|
|
3c09b95a8c | ||
|
|
780f26f135 | ||
|
|
77618674a0 | ||
|
|
c9c89a6005 | ||
|
|
d1276c4299 | ||
|
|
69730b6c95 | ||
|
|
9147d30152 | ||
|
|
6217c70fb5 | ||
|
|
17352c9a56 | ||
|
|
1b6026304f | ||
|
|
91f9973c6c | ||
|
|
ca2acee38a | ||
|
|
3bd455ced2 | ||
|
|
2c25abd143 | ||
|
|
08a9fb8cd7 | ||
|
|
d8a38b4e35 | ||
|
|
e64660f2d8 | ||
|
|
1a11466e69 | ||
|
|
78ab2fbe09 | ||
|
|
0c45d5773a | ||
|
|
f48335444b | ||
|
|
81b33cee5c | ||
|
|
020dcaad75 | ||
|
|
9bc56bcfc7 | ||
|
|
8325fadeb3 | ||
|
|
d54275b3bd | ||
|
|
29d20423a3 | ||
|
|
e539816253 | ||
|
|
922ce9ef5f | ||
|
|
b73ec6ae94 | ||
|
|
4a11bf956d | ||
|
|
3b3bb6c80a | ||
|
|
0f323ff2e3 | ||
|
|
3305c65ae6 | ||
|
|
5c5acdcb03 | ||
|
|
0e6991f486 | ||
|
|
014786cc5f | ||
|
|
223c6b50fc | ||
|
|
b18946352e | ||
|
|
96012b26b7 | ||
|
|
31cd663ad5 | ||
|
|
0e9c9d7ccf | ||
|
|
2a6eb6ec86 | ||
|
|
0a74423c07 | ||
|
|
6f1302ae67 | ||
|
|
f275570f12 | ||
|
|
90b47f6c44 | ||
|
|
e73d008695 | ||
|
|
3c5d1b402c | ||
|
|
3e22bccce4 | ||
|
|
354e140305 | ||
|
|
54859f3582 | ||
|
|
da12efd675 | ||
|
|
32f848e90d | ||
|
|
32440d23f7 | ||
|
|
62b7acc93a | ||
|
|
7b8e2c75c2 | ||
|
|
03abc641c5 | ||
|
|
fa5b98c9b5 | ||
|
|
c681be7ba8 | ||
|
|
3d3305a312 | ||
|
|
bb305af7b4 | ||
|
|
fd284973b6 | ||
|
|
5d57572694 | ||
|
|
36a22f2b3c | ||
|
|
dea9b590d1 | ||
|
|
bf843fe598 | ||
|
|
a23bc6caa8 | ||
|
|
1904fd5b02 | ||
|
|
03c9a883e1 | ||
|
|
c06df267a3 | ||
|
|
0bccdfeda7 | ||
|
|
07d118ae7d | ||
|
|
e912dde08d | ||
|
|
3544375fdd | ||
|
|
ab6ca7910e | ||
|
|
6a329f7a8e | ||
|
|
5d4bb10106 | ||
|
|
cbc079bfff | ||
|
|
09358d5853 | ||
|
|
81c345f33e | ||
|
|
3aa200eacc | ||
|
|
6ce0b60cf2 | ||
|
|
faef0784ee | ||
|
|
fb70fe5760 | ||
|
|
5b75b8a89e | ||
|
|
fa0296b796 | ||
|
|
4e2d1f568f | ||
|
|
b8d1aec1e5 | ||
|
|
38cfd6985e | ||
|
|
d7c20c6941 | ||
|
|
7894a54a0e | ||
|
|
a46e64eec3 | ||
|
|
cae0399584 | ||
|
|
bfbf4ef0b5 | ||
|
|
ca4004605e | ||
|
|
ec565a1bd3 | ||
|
|
94c4b180c1 | ||
|
|
66de0b9019 | ||
|
|
f752b38228 | ||
|
|
d79748a967 | ||
|
|
c1b6c2c5a5 | ||
|
|
806b04e0ca | ||
|
|
736b562f3a | ||
|
|
db440236fc | ||
|
|
00ea8b900a | ||
|
|
e092e9ba67 | ||
|
|
8313cf34a6 | ||
|
|
8f4b8f5826 | ||
|
|
c607b01f33 | ||
|
|
cc5d01d0e2 | ||
|
|
a4cc41b679 | ||
|
|
67361e9ed5 | ||
|
|
8662572690 | ||
|
|
99ea1788e7 | ||
|
|
7bec3c90cd | ||
|
|
ca4e6c46bc | ||
|
|
678da159a9 | ||
|
|
cc3b51fb3b | ||
|
|
060344bb5a | ||
|
|
b42c67040d | ||
|
|
f3b18fcf0e | ||
|
|
a86c69edc9 | ||
|
|
55c60a05dc | ||
|
|
ecf40cc747 | ||
|
|
197458e60b | ||
|
|
0c3ffb0743 | ||
|
|
56dffd3c58 | ||
|
|
57a3a37fdd | ||
|
|
a8a273f0d8 | ||
|
|
9f4ab26696 | ||
|
|
4ddef9d648 | ||
|
|
7cccca8194 | ||
|
|
2a8b678a4b | ||
|
|
b9767b865a | ||
|
|
f6bc3eb014 | ||
|
|
007917df19 | ||
|
|
22a2e850bf | ||
|
|
1cfdf3613c | ||
|
|
fe280e6bb1 | ||
|
|
a330fe6017 | ||
|
|
4ee4ad25b0 | ||
|
|
777a661389 | ||
|
|
8174230afe | ||
|
|
e1777dc533 | ||
|
|
390731c07b | ||
|
|
25d475e165 | ||
|
|
1e60250670 | ||
|
|
f6d2dd520c | ||
|
|
4600588e72 | ||
|
|
825ca94080 | ||
|
|
7f674f9861 | ||
|
|
27dba7e4e1 | ||
|
|
44295ff248 | ||
|
|
dc33d96a54 | ||
|
|
4ff7619356 | ||
|
|
cc5c2bd7cd | ||
|
|
2884712685 | ||
|
|
027264588b | ||
|
|
ddd75ce730 | ||
|
|
4bc13c28dd | ||
|
|
7a1db89a6e | ||
|
|
c08489509a | ||
|
|
7054ae8a88 | ||
|
|
d7e913be95 | ||
|
|
f283a2ced5 | ||
|
|
c7274ba16f | ||
|
|
6f323e379c | ||
|
|
42212b409a | ||
|
|
e8506cc5f1 | ||
|
|
d387f9f1fa | ||
|
|
73a555788d | ||
|
|
be58f67115 | ||
|
|
b26117a65d | ||
|
|
34fe6182c8 | ||
|
|
ee3ae6025f | ||
|
|
df9812b2a3 | ||
|
|
113eea04cc | ||
|
|
57f9ebdb68 | ||
|
|
94d0e28ad7 | ||
|
|
cd553d45cc | ||
|
|
d993f9ac64 | ||
|
|
833bdc13bd | ||
|
|
33657b4b49 | ||
|
|
ec6bc8e36b | ||
|
|
799370f753 | ||
|
|
abd404c57c | ||
|
|
037ed3cd54 | ||
|
|
c461a7fa15 | ||
|
|
8fc8aaa6dd | ||
|
|
2bc45e2b2e | ||
|
|
cdfc58d115 | ||
|
|
bb8a57d2e9 | ||
|
|
5e52339135 | ||
|
|
df75914e30 | ||
|
|
572e6ccb37 | ||
|
|
8e1ebe28c0 | ||
|
|
adec044e02 | ||
|
|
b9868cc709 | ||
|
|
fce8b125f8 | ||
|
|
4befd2e4ff | ||
|
|
38cdc1b7ba | ||
|
|
a0f6018469 | ||
|
|
f230d55031 | ||
|
|
2f6a15a9ae | ||
|
|
04d751208f | ||
|
|
7cfc40f328 | ||
|
|
3c54d32b6d | ||
|
|
ece7d92e57 | ||
|
|
8e736b28af | ||
|
|
92ec0a5b1d | ||
|
|
398834f690 | ||
|
|
28e6dd8759 | ||
|
|
dedc937915 | ||
|
|
732f4241fe | ||
|
|
873585e1ae | ||
|
|
71a5a02e8c | ||
|
|
51fbd02b40 | ||
|
|
763eda5038 | ||
|
|
6cdb76503b | ||
|
|
aa8edd7a47 | ||
|
|
535aa56627 | ||
|
|
db0fb30f7b | ||
|
|
7619fb4753 | ||
|
|
0aeba954d4 | ||
|
|
2bc1468fa2 | ||
|
|
4b29b6efc5 | ||
|
|
26b1003cfd | ||
|
|
b696dce6e4 | ||
|
|
403a86feca | ||
|
|
56d6a9767e | ||
|
|
d2cc229622 | ||
|
|
e6b166da7d | ||
|
|
77f401d977 | ||
|
|
7d7b232fdb | ||
|
|
959f1e33cd | ||
|
|
7f5ab96f81 | ||
|
|
443089a66f | ||
|
|
f5d9b47177 | ||
|
|
a0cddbe9b3 | ||
|
|
5f7fcfd3df | ||
|
|
a39080340a | ||
|
|
a3d6879c55 | ||
|
|
b09be86a3c | ||
|
|
02ef033d23 | ||
|
|
f10861e1de | ||
|
|
ecf53d9961 | ||
|
|
5339c09b72 | ||
|
|
b6ad218126 | ||
|
|
b7b74a429e | ||
|
|
52acd3123f | ||
|
|
c9c3a689d8 | ||
|
|
da4a2a2494 | ||
|
|
35a1fb26f9 | ||
|
|
cb3723242c | ||
|
|
6a0c6284d0 | ||
|
|
114ade7456 | ||
|
|
a01e0e37f4 | ||
|
|
18884025de | ||
|
|
cfdc941207 | ||
|
|
5eaf00ba0e | ||
|
|
8948555ac1 | ||
|
|
e28418d6d6 | ||
|
|
fe83c53206 | ||
|
|
942aa08285 | ||
|
|
93dd6b5a98 | ||
|
|
08dd9ca91c | ||
|
|
77efdc3ccf | ||
|
|
ef1bcd5afa | ||
|
|
8636685252 | ||
|
|
5873dfb731 | ||
|
|
934abec88c | ||
|
|
c1d654c4ce | ||
|
|
d64b12b14c | ||
|
|
3f4ab5f95e | ||
|
|
ee1e94be96 | ||
|
|
1e9999a8d3 | ||
|
|
460ca99fe1 | ||
|
|
7fdf9b7012 | ||
|
|
c16b869fea | ||
|
|
8deb19e3ac | ||
|
|
e6d4445a8a | ||
|
|
0f0da809da | ||
|
|
db8e805a96 | ||
|
|
a882cc7e8e | ||
|
|
acede26aa6 | ||
|
|
9330919be8 | ||
|
|
91b4e91e9c | ||
|
|
08ff286f9a | ||
|
|
bc9fe2f4f9 | ||
|
|
e99b168bd6 | ||
|
|
a7afb1f680 | ||
|
|
6f3934f2e5 | ||
|
|
79da297add | ||
|
|
6664535ab9 | ||
|
|
80d290e178 | ||
|
|
d144af6d8e |
38
.github/CODEOWNERS
vendored
38
.github/CODEOWNERS
vendored
@@ -1,41 +1,33 @@
|
||||
# Order matters. The last matching pattern takes precedence.
|
||||
|
||||
### Core ###
|
||||
/packages/payload/src/uploads/ @denolfe
|
||||
/packages/payload/src/admin/ @jmikrut @jacobsfletch @JarrodMFlesch
|
||||
### Package Exports ###
|
||||
/**/exports/ @denolfe @jmikrut
|
||||
|
||||
### Adapters ###
|
||||
/packages/db-*/ @denolfe @jmikrut @DanRibbens
|
||||
/packages/richtext-*/ @denolfe @jmikrut @DanRibbens @AlessioGr
|
||||
/packages/richtext-*/ @AlessioGr
|
||||
|
||||
### Plugins ###
|
||||
/packages/plugin-*/ @denolfe @jmikrut @DanRibbens
|
||||
/packages/plugin-cloud*/ @denolfe
|
||||
/packages/plugin-form-builder/ @jacobsfletch
|
||||
/packages/plugin-live-preview*/ @jacobsfletch
|
||||
/packages/plugin-nested-docs/ @jacobsfletch
|
||||
/packages/plugin-redirects/ @jacobsfletch
|
||||
/packages/plugin-search/ @jacobsfletch
|
||||
/packages/plugin-sentry/ @JessChowdhury
|
||||
/packages/plugin-seo/ @jacobsfletch
|
||||
/packages/plugin-stripe/ @jacobsfletch
|
||||
|
||||
### Examples ###
|
||||
/examples/ @jacobsfletch
|
||||
/examples/testing/ @JarrodMFlesch
|
||||
/examples/email/ @JessChowdhury
|
||||
/examples/whitelabel/ @JessChowdhury
|
||||
|
||||
### Templates ###
|
||||
/templates/ @jacobsfletch @denolfe
|
||||
|
||||
### Misc ###
|
||||
/packages/create-payload-app/ @denolfe
|
||||
/packages/eslint-config-payload/ @denolfe
|
||||
/packages/payload-admin-bar/ @jacobsfletch
|
||||
/packages/eslint-*/ @denolfe
|
||||
|
||||
### Build Files ###
|
||||
/**/package.json @denolfe
|
||||
|
||||
/tsconfig.json @denolfe
|
||||
/**/tsconfig*.json @denolfe
|
||||
|
||||
/jest.config.js @denolfe
|
||||
/**/jest.config.js @denolfe
|
||||
|
||||
### Root ###
|
||||
/package.json @denolfe
|
||||
/scripts/ @denolfe
|
||||
/.husky/ @denolfe
|
||||
/.vscode/ @denolfe
|
||||
/.github/ @denolfe
|
||||
/.github/CODEOWNERS @denolfe
|
||||
|
||||
212
.github/workflows/main.yml
vendored
212
.github/workflows/main.yml
vendored
@@ -4,7 +4,15 @@ on:
|
||||
pull_request:
|
||||
types: [opened, reopened, synchronize]
|
||||
push:
|
||||
branches: ['main', 'alpha']
|
||||
branches: ['main', 'beta']
|
||||
|
||||
concurrency:
|
||||
group: ${{ github.workflow }}-${{ github.ref }}
|
||||
cancel-in-progress: true
|
||||
|
||||
env:
|
||||
NODE_VERSION: 18.20.2
|
||||
PNPM_VERSION: 8.15.7
|
||||
|
||||
jobs:
|
||||
changes:
|
||||
@@ -15,6 +23,10 @@ jobs:
|
||||
needs_build: ${{ steps.filter.outputs.needs_build }}
|
||||
templates: ${{ steps.filter.outputs.templates }}
|
||||
steps:
|
||||
# https://github.com/actions/virtual-environments/issues/1187
|
||||
- name: tune linux network
|
||||
run: sudo ethtool -K eth0 tx off rx off
|
||||
|
||||
- uses: actions/checkout@v4
|
||||
with:
|
||||
fetch-depth: 25
|
||||
@@ -35,7 +47,7 @@ jobs:
|
||||
echo "needs_build: ${{ steps.filter.outputs.needs_build }}"
|
||||
echo "templates: ${{ steps.filter.outputs.templates }}"
|
||||
|
||||
core-build:
|
||||
build:
|
||||
needs: changes
|
||||
if: ${{ needs.changes.outputs.needs_build == 'true' }}
|
||||
runs-on: ubuntu-latest
|
||||
@@ -45,15 +57,19 @@ jobs:
|
||||
with:
|
||||
fetch-depth: 25
|
||||
|
||||
- name: Use Node.js 18
|
||||
# https://github.com/actions/virtual-environments/issues/1187
|
||||
- name: tune linux network
|
||||
run: sudo ethtool -K eth0 tx off rx off
|
||||
|
||||
- name: Setup Node@${{ env.NODE_VERSION }}
|
||||
uses: actions/setup-node@v4
|
||||
with:
|
||||
node-version: 18
|
||||
node-version: ${{ env.NODE_VERSION }}
|
||||
|
||||
- name: Install pnpm
|
||||
uses: pnpm/action-setup@v3
|
||||
with:
|
||||
version: 8
|
||||
version: ${{ env.PNPM_VERSION }}
|
||||
run_install: false
|
||||
|
||||
- name: Get pnpm store directory
|
||||
@@ -61,65 +77,61 @@ jobs:
|
||||
run: |
|
||||
echo "STORE_PATH=$(pnpm store path --silent)" >> $GITHUB_ENV
|
||||
|
||||
- uses: actions/cache@v4
|
||||
name: Setup pnpm cache
|
||||
- name: Setup pnpm cache
|
||||
uses: actions/cache@v4
|
||||
timeout-minutes: 720
|
||||
with:
|
||||
path: ${{ env.STORE_PATH }}
|
||||
key: ${{ runner.os }}-pnpm-store-${{ hashFiles('**/pnpm-lock.yaml') }}
|
||||
key: pnpm-store-${{ hashFiles('**/pnpm-lock.yaml') }}
|
||||
restore-keys: |
|
||||
${{ runner.os }}-pnpm-store-
|
||||
${{ runner.os }}-pnpm-store-${{ hashFiles('**/pnpm-lock.yaml') }}
|
||||
pnpm-store-
|
||||
pnpm-store-${{ hashFiles('**/pnpm-lock.yaml') }}
|
||||
|
||||
- run: pnpm install
|
||||
- run: pnpm run build:core
|
||||
- run: pnpm run build:all
|
||||
|
||||
- name: Cache build
|
||||
uses: actions/cache@v4
|
||||
timeout-minutes: 10
|
||||
with:
|
||||
path: ./*
|
||||
key: ${{ github.sha }}-${{ github.run_number }}
|
||||
|
||||
plugins-build:
|
||||
needs: changes
|
||||
if: ${{ needs.changes.outputs.needs_build == 'true' }}
|
||||
tests-unit:
|
||||
runs-on: ubuntu-latest
|
||||
needs: build
|
||||
|
||||
steps:
|
||||
- uses: actions/checkout@v4
|
||||
with:
|
||||
fetch-depth: 25
|
||||
# https://github.com/actions/virtual-environments/issues/1187
|
||||
- name: tune linux network
|
||||
run: sudo ethtool -K eth0 tx off rx off
|
||||
|
||||
- name: Use Node.js 18
|
||||
- name: Setup Node@${{ env.NODE_VERSION }}
|
||||
uses: actions/setup-node@v4
|
||||
with:
|
||||
node-version: 18
|
||||
node-version: ${{ env.NODE_VERSION }}
|
||||
|
||||
- name: Install pnpm
|
||||
uses: pnpm/action-setup@v3
|
||||
with:
|
||||
version: 8
|
||||
version: ${{ env.PNPM_VERSION }}
|
||||
run_install: false
|
||||
|
||||
- name: Get pnpm store directory
|
||||
shell: bash
|
||||
run: |
|
||||
echo "STORE_PATH=$(pnpm store path --silent)" >> $GITHUB_ENV
|
||||
|
||||
- uses: actions/cache@v4
|
||||
name: Setup pnpm cache
|
||||
- name: Restore build
|
||||
uses: actions/cache@v4
|
||||
timeout-minutes: 10
|
||||
with:
|
||||
path: ${{ env.STORE_PATH }}
|
||||
key: ${{ runner.os }}-pnpm-store-${{ hashFiles('**/pnpm-lock.yaml') }}
|
||||
restore-keys: |
|
||||
${{ runner.os }}-pnpm-store-
|
||||
${{ runner.os }}-pnpm-store-${{ hashFiles('**/pnpm-lock.yaml') }}
|
||||
path: ./*
|
||||
key: ${{ github.sha }}-${{ github.run_number }}
|
||||
|
||||
- run: pnpm install
|
||||
- run: pnpm run build:plugins
|
||||
- name: Unit Tests
|
||||
run: pnpm test:unit
|
||||
env:
|
||||
NODE_OPTIONS: --max-old-space-size=8096
|
||||
|
||||
tests:
|
||||
tests-int:
|
||||
runs-on: ubuntu-latest
|
||||
needs: core-build
|
||||
needs: build
|
||||
strategy:
|
||||
fail-fast: false
|
||||
matrix:
|
||||
@@ -139,19 +151,24 @@ jobs:
|
||||
AWS_REGION: us-east-1
|
||||
|
||||
steps:
|
||||
- name: Use Node.js 18
|
||||
# https://github.com/actions/virtual-environments/issues/1187
|
||||
- name: tune linux network
|
||||
run: sudo ethtool -K eth0 tx off rx off
|
||||
|
||||
- name: Setup Node@${{ env.NODE_VERSION }}
|
||||
uses: actions/setup-node@v4
|
||||
with:
|
||||
node-version: 18
|
||||
node-version: ${{ env.NODE_VERSION }}
|
||||
|
||||
- name: Install pnpm
|
||||
uses: pnpm/action-setup@v3
|
||||
with:
|
||||
version: 8
|
||||
version: ${{ env.PNPM_VERSION }}
|
||||
run_install: false
|
||||
|
||||
- name: Restore build
|
||||
uses: actions/cache@v4
|
||||
timeout-minutes: 10
|
||||
with:
|
||||
path: ./*
|
||||
key: ${{ github.sha }}-${{ github.run_number }}
|
||||
@@ -202,7 +219,7 @@ jobs:
|
||||
if: matrix.database == 'supabase'
|
||||
|
||||
- name: Integration Tests
|
||||
run: pnpm test:int --testPathIgnorePatterns=test/fields # Ignore fields tests until reworked
|
||||
run: pnpm test:int
|
||||
env:
|
||||
NODE_OPTIONS: --max-old-space-size=8096
|
||||
PAYLOAD_DATABASE: ${{ matrix.database }}
|
||||
@@ -210,7 +227,7 @@ jobs:
|
||||
|
||||
tests-e2e:
|
||||
runs-on: ubuntu-latest
|
||||
needs: core-build
|
||||
needs: build
|
||||
strategy:
|
||||
fail-fast: false
|
||||
matrix:
|
||||
@@ -218,75 +235,89 @@ jobs:
|
||||
suite:
|
||||
- _community
|
||||
- access-control
|
||||
# - admin
|
||||
- admin
|
||||
- auth
|
||||
# - field-error-states
|
||||
# - fields-relationship
|
||||
# - fields
|
||||
- fields/lexical
|
||||
- field-error-states
|
||||
- fields-relationship
|
||||
- fields
|
||||
- fields__collections__Blocks
|
||||
- fields__collections__Array
|
||||
- fields__collections__Relationship
|
||||
- fields__collections__Lexical
|
||||
- live-preview
|
||||
# - localization
|
||||
# - plugin-nested-docs
|
||||
# - plugin-seo
|
||||
# - refresh-permissions
|
||||
# - uploads
|
||||
# - versions
|
||||
- localization
|
||||
- plugin-cloud-storage
|
||||
- plugin-form-builder
|
||||
- plugin-nested-docs
|
||||
- plugin-seo
|
||||
- versions
|
||||
- uploads
|
||||
|
||||
steps:
|
||||
- name: Use Node.js 18
|
||||
# https://github.com/actions/virtual-environments/issues/1187
|
||||
- name: tune linux network
|
||||
run: sudo ethtool -K eth0 tx off rx off
|
||||
|
||||
- name: Setup Node@${{ env.NODE_VERSION }}
|
||||
uses: actions/setup-node@v4
|
||||
with:
|
||||
node-version: 18
|
||||
node-version: ${{ env.NODE_VERSION }}
|
||||
|
||||
- name: Install pnpm
|
||||
uses: pnpm/action-setup@v3
|
||||
with:
|
||||
version: 8
|
||||
version: ${{ env.PNPM_VERSION }}
|
||||
run_install: false
|
||||
|
||||
- name: Restore build
|
||||
uses: actions/cache@v4
|
||||
timeout-minutes: 10
|
||||
with:
|
||||
path: ./*
|
||||
key: ${{ github.sha }}-${{ github.run_number }}
|
||||
|
||||
- name: Start LocalStack
|
||||
run: pnpm docker:start
|
||||
if: ${{ matrix.suite == 'plugin-cloud-storage' }}
|
||||
|
||||
- name: Install Playwright
|
||||
run: pnpm exec playwright install
|
||||
run: pnpm exec playwright install --with-deps
|
||||
|
||||
- name: E2E Tests
|
||||
uses: nick-fields/retry@v3
|
||||
with:
|
||||
retry_on: error
|
||||
max_attempts: 2
|
||||
timeout_minutes: 15
|
||||
command: pnpm test:e2e ${{ matrix.suite }}
|
||||
run: pnpm test:e2e ${{ matrix.suite }}
|
||||
|
||||
- uses: actions/upload-artifact@v4
|
||||
if: always()
|
||||
with:
|
||||
name: test-results
|
||||
name: test-results-${{ matrix.suite }}
|
||||
path: test/test-results/
|
||||
if-no-files-found: ignore
|
||||
retention-days: 1
|
||||
|
||||
tests-type-generation:
|
||||
if: false # This should be replaced with gen on a real Payload project
|
||||
runs-on: ubuntu-latest
|
||||
needs: core-build
|
||||
needs: build
|
||||
|
||||
steps:
|
||||
- name: Use Node.js 18
|
||||
# https://github.com/actions/virtual-environments/issues/1187
|
||||
- name: tune linux network
|
||||
run: sudo ethtool -K eth0 tx off rx off
|
||||
|
||||
- name: Setup Node@${{ env.NODE_VERSION }}
|
||||
uses: actions/setup-node@v4
|
||||
with:
|
||||
node-version: 18
|
||||
node-version: ${{ env.NODE_VERSION }}
|
||||
|
||||
- name: Install pnpm
|
||||
uses: pnpm/action-setup@v3
|
||||
with:
|
||||
version: 8
|
||||
version: ${{ env.PNPM_VERSION }}
|
||||
run_install: false
|
||||
|
||||
- name: Restore build
|
||||
uses: actions/cache@v4
|
||||
timeout-minutes: 10
|
||||
with:
|
||||
path: ./*
|
||||
key: ${{ github.sha }}-${{ github.run_number }}
|
||||
@@ -297,46 +328,6 @@ jobs:
|
||||
- name: Generate GraphQL schema file
|
||||
run: pnpm dev:generate-graphql-schema graphql-schema-gen
|
||||
|
||||
plugins:
|
||||
runs-on: ubuntu-latest
|
||||
needs: core-build
|
||||
strategy:
|
||||
fail-fast: false
|
||||
matrix:
|
||||
pkg:
|
||||
- create-payload-app
|
||||
- plugin-cloud
|
||||
- plugin-cloud-storage
|
||||
- plugin-form-builder
|
||||
- plugin-nested-docs
|
||||
- plugin-search
|
||||
- plugin-sentry
|
||||
- plugin-seo
|
||||
|
||||
steps:
|
||||
- name: Use Node.js 18
|
||||
uses: actions/setup-node@v4
|
||||
with:
|
||||
node-version: 18
|
||||
|
||||
- name: Install pnpm
|
||||
uses: pnpm/action-setup@v3
|
||||
with:
|
||||
version: 8
|
||||
run_install: false
|
||||
|
||||
- name: Restore build
|
||||
uses: actions/cache@v4
|
||||
with:
|
||||
path: ./*
|
||||
key: ${{ github.sha }}-${{ github.run_number }}
|
||||
|
||||
- name: Build ${{ matrix.pkg }}
|
||||
run: pnpm turbo run build --filter=${{ matrix.pkg }}
|
||||
|
||||
- name: Test ${{ matrix.pkg }}
|
||||
run: pnpm --filter ${{ matrix.pkg }} run test
|
||||
|
||||
templates:
|
||||
needs: changes
|
||||
if: false # Disable until templates are updated for 3.0
|
||||
@@ -350,11 +341,14 @@ jobs:
|
||||
- uses: actions/checkout@v4
|
||||
with:
|
||||
fetch-depth: 25
|
||||
# https://github.com/actions/virtual-environments/issues/1187
|
||||
- name: tune linux network
|
||||
run: sudo ethtool -K eth0 tx off rx off
|
||||
|
||||
- name: Use Node.js 18
|
||||
- name: Setup Node@${{ env.NODE_VERSION }}
|
||||
uses: actions/setup-node@v4
|
||||
with:
|
||||
node-version: 18
|
||||
node-version: ${{ env.NODE_VERSION }}
|
||||
|
||||
- name: Start MongoDB
|
||||
uses: supercharge/mongodb-github-action@1.10.0
|
||||
|
||||
97
.github/workflows/pr-title.yml
vendored
Normal file
97
.github/workflows/pr-title.yml
vendored
Normal file
@@ -0,0 +1,97 @@
|
||||
name: pr-title
|
||||
|
||||
on:
|
||||
pull_request:
|
||||
types:
|
||||
- opened
|
||||
- edited
|
||||
- synchronize
|
||||
|
||||
permissions:
|
||||
pull-requests: write
|
||||
|
||||
jobs:
|
||||
main:
|
||||
name: lint-pr-title
|
||||
runs-on: ubuntu-latest
|
||||
steps:
|
||||
- uses: amannn/action-semantic-pull-request@v5
|
||||
id: lint_pr_title
|
||||
env:
|
||||
GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
|
||||
with:
|
||||
types: |
|
||||
build
|
||||
chore
|
||||
ci
|
||||
docs
|
||||
feat
|
||||
fix
|
||||
perf
|
||||
refactor
|
||||
revert
|
||||
style
|
||||
test
|
||||
types
|
||||
scopes: |
|
||||
cpa
|
||||
db-\*
|
||||
db-mongodb
|
||||
db-postgres
|
||||
email-nodemailer
|
||||
eslint
|
||||
graphql
|
||||
live-preview
|
||||
live-preview-react
|
||||
next
|
||||
payload
|
||||
plugin-cloud
|
||||
plugin-cloud-storage
|
||||
plugin-form-builder
|
||||
plugin-nested-docs
|
||||
plugin-redirects
|
||||
plugin-search
|
||||
plugin-sentry
|
||||
plugin-seo
|
||||
plugin-stripe
|
||||
richtext-\*
|
||||
richtext-lexical
|
||||
richtext-slate
|
||||
storage-\*
|
||||
storage-azure
|
||||
storage-gcs
|
||||
storage-vercel-blob
|
||||
storage-s3
|
||||
translations
|
||||
ui
|
||||
templates
|
||||
examples
|
||||
|
||||
# Disallow uppercase letters at the beginning of the subject
|
||||
subjectPattern: ^(?![A-Z]).+$
|
||||
|
||||
- uses: marocchino/sticky-pull-request-comment@v2
|
||||
# When the previous steps fails, the workflow would stop. By adding this
|
||||
# condition you can continue the execution with the populated error message.
|
||||
if: always() && (steps.lint_pr_title.outputs.error_message != null)
|
||||
with:
|
||||
header: pr-title-lint-error
|
||||
message: |
|
||||
Pull Request titles must follow the [Conventional Commits specification](https://www.conventionalcommits.org/en/v1.0.0/) and have valid scopes.
|
||||
|
||||
${{ steps.lint_pr_title.outputs.error_message }}
|
||||
|
||||
```
|
||||
feat(ui): add Button component
|
||||
^ ^ ^
|
||||
| | |__ Subject
|
||||
| |_______ Scope
|
||||
|____________ Type
|
||||
```
|
||||
|
||||
# Delete a previous comment when the issue has been resolved
|
||||
- if: ${{ steps.lint_pr_title.outputs.error_message == null }}
|
||||
uses: marocchino/sticky-pull-request-comment@v2
|
||||
with:
|
||||
header: pr-title-lint-error
|
||||
delete: true
|
||||
4
.gitignore
vendored
4
.gitignore
vendored
@@ -3,6 +3,7 @@ package-lock.json
|
||||
dist
|
||||
/.idea/*
|
||||
!/.idea/runConfigurations
|
||||
!/.idea/payload.iml
|
||||
|
||||
test-results
|
||||
.devcontainer
|
||||
@@ -15,6 +16,7 @@ test-results
|
||||
|
||||
# Ignore test directory media folder/files
|
||||
/media
|
||||
test/media
|
||||
/versions
|
||||
|
||||
# Created by https://www.toptal.com/developers/gitignore/api/node,macos,windows,webstorm,sublimetext,visualstudiocode
|
||||
@@ -288,4 +290,4 @@ $RECYCLE.BIN/
|
||||
# End of https://www.toptal.com/developers/gitignore/api/node,macos,windows,webstorm,sublimetext,visualstudiocode
|
||||
|
||||
/build
|
||||
.swc
|
||||
.swc
|
||||
|
||||
54
.idea/payload.iml
generated
Normal file
54
.idea/payload.iml
generated
Normal file
@@ -0,0 +1,54 @@
|
||||
<?xml version="1.0" encoding="UTF-8"?>
|
||||
<module type="WEB_MODULE" version="4">
|
||||
<component name="NewModuleRootManager">
|
||||
<content url="file://$MODULE_DIR$">
|
||||
<excludeFolder url="file://$MODULE_DIR$/.tmp" />
|
||||
<excludeFolder url="file://$MODULE_DIR$/temp" />
|
||||
<excludeFolder url="file://$MODULE_DIR$/tmp" />
|
||||
<excludeFolder url="file://$MODULE_DIR$/packages/payload/.turbo" />
|
||||
<excludeFolder url="file://$MODULE_DIR$/packages/payload/components" />
|
||||
<excludeFolder url="file://$MODULE_DIR$/packages/payload/dist" />
|
||||
<excludeFolder url="file://$MODULE_DIR$/.swc" />
|
||||
<excludeFolder url="file://$MODULE_DIR$/.turbo" />
|
||||
<excludeFolder url="file://$MODULE_DIR$/examples" />
|
||||
<excludeFolder url="file://$MODULE_DIR$/media" />
|
||||
<excludeFolder url="file://$MODULE_DIR$/packages/create-payload-app/.turbo" />
|
||||
<excludeFolder url="file://$MODULE_DIR$/packages/create-payload-app/dist" />
|
||||
<excludeFolder url="file://$MODULE_DIR$/packages/db-mongodb/.turbo" />
|
||||
<excludeFolder url="file://$MODULE_DIR$/packages/db-mongodb/dist" />
|
||||
<excludeFolder url="file://$MODULE_DIR$/packages/db-postgres/.turbo" />
|
||||
<excludeFolder url="file://$MODULE_DIR$/packages/db-postgres/dist" />
|
||||
<excludeFolder url="file://$MODULE_DIR$/packages/graphql/.turbo" />
|
||||
<excludeFolder url="file://$MODULE_DIR$/packages/graphql/dist" />
|
||||
<excludeFolder url="file://$MODULE_DIR$/packages/live-preview-react/.turbo" />
|
||||
<excludeFolder url="file://$MODULE_DIR$/packages/live-preview-react/dist" />
|
||||
<excludeFolder url="file://$MODULE_DIR$/packages/live-preview/.turbo" />
|
||||
<excludeFolder url="file://$MODULE_DIR$/packages/live-preview/dist" />
|
||||
<excludeFolder url="file://$MODULE_DIR$/packages/next/.swc" />
|
||||
<excludeFolder url="file://$MODULE_DIR$/packages/next/.turbo" />
|
||||
<excludeFolder url="file://$MODULE_DIR$/packages/payload/fields" />
|
||||
<excludeFolder url="file://$MODULE_DIR$/packages/plugin-cloud-storage/.turbo" />
|
||||
<excludeFolder url="file://$MODULE_DIR$/packages/plugin-cloud-storage/dist" />
|
||||
<excludeFolder url="file://$MODULE_DIR$/packages/plugin-cloud/.turbo" />
|
||||
<excludeFolder url="file://$MODULE_DIR$/packages/plugin-cloud/dist" />
|
||||
<excludeFolder url="file://$MODULE_DIR$/packages/plugin-form-builder/.turbo" />
|
||||
<excludeFolder url="file://$MODULE_DIR$/packages/plugin-nested-docs/.turbo" />
|
||||
<excludeFolder url="file://$MODULE_DIR$/packages/plugin-nested-docs/dist" />
|
||||
<excludeFolder url="file://$MODULE_DIR$/packages/plugin-redirects/.turbo" />
|
||||
<excludeFolder url="file://$MODULE_DIR$/packages/plugin-redirects/dist" />
|
||||
<excludeFolder url="file://$MODULE_DIR$/packages/plugin-search/.turbo" />
|
||||
<excludeFolder url="file://$MODULE_DIR$/packages/plugin-search/dist" />
|
||||
<excludeFolder url="file://$MODULE_DIR$/packages/plugin-sentry/.turbo" />
|
||||
<excludeFolder url="file://$MODULE_DIR$/packages/plugin-seo/.turbo" />
|
||||
<excludeFolder url="file://$MODULE_DIR$/packages/plugin-seo/dist" />
|
||||
<excludeFolder url="file://$MODULE_DIR$/packages/plugin-stripe/.turbo" />
|
||||
<excludeFolder url="file://$MODULE_DIR$/packages/richtext-lexical/.turbo" />
|
||||
<excludeFolder url="file://$MODULE_DIR$/packages/richtext-lexical/dist" />
|
||||
<excludeFolder url="file://$MODULE_DIR$/templates" />
|
||||
<excludeFolder url="file://$MODULE_DIR$/test/.swc" />
|
||||
<excludeFolder url="file://$MODULE_DIR$/versions" />
|
||||
</content>
|
||||
<orderEntry type="inheritedJdk" />
|
||||
<orderEntry type="sourceFolder" forTests="false" />
|
||||
</component>
|
||||
</module>
|
||||
@@ -1 +1 @@
|
||||
v18.19.1
|
||||
v18.20.2
|
||||
|
||||
@@ -10,3 +10,5 @@
|
||||
**/temp
|
||||
**/docs/**
|
||||
tsconfig.json
|
||||
packages/payload/*.js
|
||||
packages/payload/*.d.ts
|
||||
|
||||
54
.vscode/launch.json
vendored
54
.vscode/launch.json
vendored
@@ -10,19 +10,44 @@
|
||||
"cwd": "${workspaceFolder}"
|
||||
},
|
||||
{
|
||||
"command": "node --no-deprecation test/dev.js fields",
|
||||
"command": "node --no-deprecation test/dev.js _community",
|
||||
"cwd": "${workspaceFolder}",
|
||||
"name": "Run Dev Community",
|
||||
"request": "launch",
|
||||
"type": "node-terminal"
|
||||
},
|
||||
{
|
||||
"command": "pnpm run dev live-preview -- --no-turbo",
|
||||
"command": "node --no-deprecation test/dev.js live-preview",
|
||||
"cwd": "${workspaceFolder}",
|
||||
"name": "Run Dev Live Preview",
|
||||
"request": "launch",
|
||||
"type": "node-terminal"
|
||||
},
|
||||
{
|
||||
"command": "node --no-deprecation test/loader/init.js",
|
||||
"cwd": "${workspaceFolder}",
|
||||
"name": "Run Loader",
|
||||
"request": "launch",
|
||||
"type": "node-terminal",
|
||||
"env": {
|
||||
"LOADER_TEST_FILE_PATH": "./dependency-test.js"
|
||||
// "LOADER_TEST_FILE_PATH": "../fields/config.ts"
|
||||
}
|
||||
},
|
||||
{
|
||||
"command": "node --no-deprecation test/dev.js admin",
|
||||
"cwd": "${workspaceFolder}",
|
||||
"name": "Run Dev Admin",
|
||||
"request": "launch",
|
||||
"type": "node-terminal"
|
||||
},
|
||||
{
|
||||
"command": "node --no-deprecation test/dev.js auth",
|
||||
"cwd": "${workspaceFolder}",
|
||||
"name": "Run Dev Auth",
|
||||
"request": "launch",
|
||||
"type": "node-terminal"
|
||||
},
|
||||
{
|
||||
"command": "pnpm run dev plugin-cloud-storage",
|
||||
"cwd": "${workspaceFolder}",
|
||||
@@ -34,50 +59,43 @@
|
||||
}
|
||||
},
|
||||
{
|
||||
"command": "pnpm run dev fields",
|
||||
"command": "node --no-deprecation test/dev.js fields",
|
||||
"cwd": "${workspaceFolder}",
|
||||
"name": "Run Dev Fields",
|
||||
"request": "launch",
|
||||
"type": "node-terminal"
|
||||
},
|
||||
{
|
||||
"command": "pnpm run dev:postgres versions",
|
||||
"command": "node --no-deprecation test/dev.js versions",
|
||||
"cwd": "${workspaceFolder}",
|
||||
"name": "Run Dev Postgres",
|
||||
"request": "launch",
|
||||
"type": "node-terminal"
|
||||
"type": "node-terminal",
|
||||
"env": {
|
||||
"PAYLOAD_DATABASE": "postgres"
|
||||
}
|
||||
},
|
||||
{
|
||||
"command": "pnpm run dev versions",
|
||||
"command": "node --no-deprecation test/dev.js versions",
|
||||
"cwd": "${workspaceFolder}",
|
||||
"name": "Run Dev Versions",
|
||||
"request": "launch",
|
||||
"type": "node-terminal"
|
||||
},
|
||||
{
|
||||
"command": "pnpm run dev localization",
|
||||
"command": "node --no-deprecation test/dev.js localization",
|
||||
"cwd": "${workspaceFolder}",
|
||||
"name": "Run Dev Localization",
|
||||
"request": "launch",
|
||||
"type": "node-terminal"
|
||||
},
|
||||
{
|
||||
"command": "pnpm run dev uploads",
|
||||
"command": "node --no-deprecation test/dev.js uploads",
|
||||
"cwd": "${workspaceFolder}",
|
||||
"name": "Run Dev Uploads",
|
||||
"request": "launch",
|
||||
"type": "node-terminal"
|
||||
},
|
||||
{
|
||||
"command": "PAYLOAD_BUNDLER=vite pnpm run dev fields",
|
||||
"cwd": "${workspaceFolder}",
|
||||
"name": "Run Dev Fields (Vite)",
|
||||
"request": "launch",
|
||||
"type": "node-terminal",
|
||||
"env": {
|
||||
"NODE_ENV": "production"
|
||||
}
|
||||
},
|
||||
{
|
||||
"command": "pnpm run test:int live-preview",
|
||||
"cwd": "${workspaceFolder}",
|
||||
|
||||
@@ -1,7 +1,9 @@
|
||||
/* THIS FILE WAS GENERATED AUTOMATICALLY BY PAYLOAD. */
|
||||
import type { Metadata } from 'next'
|
||||
|
||||
import config from '@payload-config'
|
||||
/* DO NOT MODIFY IT BECAUSE IT COULD BE REWRITTEN AT ANY TIME. */
|
||||
import { NotFoundView } from '@payloadcms/next/views/NotFound/index.js'
|
||||
import { NotFoundPage, generatePageMetadata } from '@payloadcms/next/views'
|
||||
|
||||
type Args = {
|
||||
params: {
|
||||
@@ -12,6 +14,9 @@ type Args = {
|
||||
}
|
||||
}
|
||||
|
||||
const NotFound = ({ params, searchParams }: Args) => NotFoundView({ config, params, searchParams })
|
||||
export const generateMetadata = ({ params, searchParams }: Args): Promise<Metadata> =>
|
||||
generatePageMetadata({ config, params, searchParams })
|
||||
|
||||
const NotFound = ({ params, searchParams }: Args) => NotFoundPage({ config, params, searchParams })
|
||||
|
||||
export default NotFound
|
||||
|
||||
@@ -3,7 +3,7 @@ import type { Metadata } from 'next'
|
||||
|
||||
import config from '@payload-config'
|
||||
/* DO NOT MODIFY IT BECAUSE IT COULD BE REWRITTEN AT ANY TIME. */
|
||||
import { RootPage, generatePageMetadata } from '@payloadcms/next/views/Root/index.js'
|
||||
import { RootPage, generatePageMetadata } from '@payloadcms/next/views'
|
||||
|
||||
type Args = {
|
||||
params: {
|
||||
|
||||
@@ -1,9 +1,10 @@
|
||||
/* THIS FILE WAS GENERATED AUTOMATICALLY BY PAYLOAD. */
|
||||
/* DO NOT MODIFY it because it could be re-written at any time. */
|
||||
import config from '@payload-config'
|
||||
import { REST_DELETE, REST_GET, REST_PATCH, REST_POST } from '@payloadcms/next/routes/index.js'
|
||||
import { REST_DELETE, REST_GET, REST_OPTIONS, REST_PATCH, REST_POST } from '@payloadcms/next/routes'
|
||||
|
||||
export const GET = REST_GET(config)
|
||||
export const POST = REST_POST(config)
|
||||
export const DELETE = REST_DELETE(config)
|
||||
export const PATCH = REST_PATCH(config)
|
||||
export const OPTIONS = REST_OPTIONS(config)
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
/* THIS FILE WAS GENERATED AUTOMATICALLY BY PAYLOAD. */
|
||||
/* DO NOT MODIFY it because it could be re-written at any time. */
|
||||
import config from '@payload-config'
|
||||
import { GRAPHQL_PLAYGROUND_GET } from '@payloadcms/next/routes/index.js'
|
||||
import { GRAPHQL_PLAYGROUND_GET } from '@payloadcms/next/routes'
|
||||
|
||||
export const GET = GRAPHQL_PLAYGROUND_GET(config)
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
/* THIS FILE WAS GENERATED AUTOMATICALLY BY PAYLOAD. */
|
||||
/* DO NOT MODIFY it because it could be re-written at any time. */
|
||||
import config from '@payload-config'
|
||||
import { GRAPHQL_POST } from '@payloadcms/next/routes/index.js'
|
||||
import { GRAPHQL_POST } from '@payloadcms/next/routes'
|
||||
|
||||
export const POST = GRAPHQL_POST(config)
|
||||
|
||||
@@ -0,0 +1,8 @@
|
||||
#custom-css {
|
||||
font-family: monospace;
|
||||
background-image: url('/placeholder.png');
|
||||
}
|
||||
|
||||
#custom-css::after {
|
||||
content: 'custom-css';
|
||||
}
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
/* THIS FILE WAS GENERATED AUTOMATICALLY BY PAYLOAD. */
|
||||
import configPromise from '@payload-config'
|
||||
import { RootLayout } from '@payloadcms/next/layouts/Root/index.js'
|
||||
import { RootLayout } from '@payloadcms/next/layouts'
|
||||
/* DO NOT MODIFY IT BECAUSE IT COULD BE REWRITTEN AT ANY TIME. */
|
||||
import React from 'react'
|
||||
|
||||
|
||||
@@ -1,35 +0,0 @@
|
||||
import QueryString from 'qs'
|
||||
|
||||
import { PAYLOAD_SERVER_URL } from './serverURL.js'
|
||||
|
||||
export const fetchDoc = async <T>(args: {
|
||||
collection: string
|
||||
depth?: number
|
||||
id?: string
|
||||
slug?: string
|
||||
}): Promise<T> => {
|
||||
const { id, slug, collection, depth = 2 } = args || {}
|
||||
|
||||
const queryString = QueryString.stringify(
|
||||
{
|
||||
...(slug ? { 'where[slug][equals]': slug } : {}),
|
||||
...(depth ? { depth } : {}),
|
||||
},
|
||||
{ addQueryPrefix: true },
|
||||
)
|
||||
|
||||
const doc: T = await fetch(`${PAYLOAD_SERVER_URL}/api/${collection}${queryString}`, {
|
||||
cache: 'no-store',
|
||||
headers: {
|
||||
'Content-Type': 'application/json',
|
||||
},
|
||||
method: 'GET',
|
||||
})
|
||||
?.then((res) => res.json())
|
||||
?.then((res) => {
|
||||
if (res.errors) throw new Error(res?.errors?.[0]?.message ?? 'Error fetching doc')
|
||||
return res?.docs?.[0]
|
||||
})
|
||||
|
||||
return doc
|
||||
}
|
||||
@@ -1,19 +0,0 @@
|
||||
import { PAYLOAD_SERVER_URL } from './serverURL.js'
|
||||
|
||||
export const fetchDocs = async <T>(collection: string): Promise<T[]> => {
|
||||
const docs: T[] = await fetch(`${PAYLOAD_SERVER_URL}/api/${collection}?depth=0&limit=100`, {
|
||||
cache: 'no-store',
|
||||
headers: {
|
||||
'Content-Type': 'application/json',
|
||||
},
|
||||
method: 'GET',
|
||||
})
|
||||
?.then((res) => res.json())
|
||||
?.then((res) => {
|
||||
if (res.errors) throw new Error(res?.errors?.[0]?.message ?? 'Error fetching docs')
|
||||
|
||||
return res?.docs
|
||||
})
|
||||
|
||||
return docs
|
||||
}
|
||||
@@ -1,25 +0,0 @@
|
||||
import type { Footer } from '../../../test/live-preview/payload-types.js'
|
||||
|
||||
import { PAYLOAD_SERVER_URL } from './serverURL.js'
|
||||
|
||||
export async function fetchFooter(): Promise<Footer> {
|
||||
if (!PAYLOAD_SERVER_URL) throw new Error('PAYLOAD_SERVER_URL not found')
|
||||
|
||||
const footer = await fetch(`${PAYLOAD_SERVER_URL}/api/globals/footer`, {
|
||||
cache: 'no-store',
|
||||
headers: {
|
||||
'Content-Type': 'application/json',
|
||||
},
|
||||
method: 'GET',
|
||||
})
|
||||
.then((res) => {
|
||||
if (!res.ok) throw new Error('Error fetching doc')
|
||||
return res.json()
|
||||
})
|
||||
?.then((res) => {
|
||||
if (res?.errors) throw new Error(res?.errors[0]?.message || 'Error fetching footer')
|
||||
return res
|
||||
})
|
||||
|
||||
return footer
|
||||
}
|
||||
@@ -1,25 +0,0 @@
|
||||
import type { Header } from '../../../test/live-preview/payload-types.js'
|
||||
|
||||
import { PAYLOAD_SERVER_URL } from './serverURL.js'
|
||||
|
||||
export async function fetchHeader(): Promise<Header> {
|
||||
if (!PAYLOAD_SERVER_URL) throw new Error('PAYLOAD_SERVER_URL not found')
|
||||
|
||||
const header = await fetch(`${PAYLOAD_SERVER_URL}/api/globals/header`, {
|
||||
cache: 'no-store',
|
||||
headers: {
|
||||
'Content-Type': 'application/json',
|
||||
},
|
||||
method: 'GET',
|
||||
})
|
||||
?.then((res) => {
|
||||
if (!res.ok) throw new Error('Error fetching doc')
|
||||
return res.json()
|
||||
})
|
||||
?.then((res) => {
|
||||
if (res?.errors) throw new Error(res?.errors[0]?.message || 'Error fetching header')
|
||||
return res
|
||||
})
|
||||
|
||||
return header
|
||||
}
|
||||
@@ -639,12 +639,12 @@ export const CustomArrayManager = () => {
|
||||
|
||||
The `useCollapsible` hook allows you to control parent collapsibles:
|
||||
|
||||
| Property | Description |
|
||||
| ----------------------- | ------------------------------------------------------------------------------------------------------------ | --- |
|
||||
| **`collapsed`** | State of the collapsible. `true` if open, `false` if collapsed |
|
||||
| **`isVisible`** | If nested, determine if the nearest collapsible is visible. `true` if no parent is closed, `false` otherwise |
|
||||
| **`toggle`** | Toggles the state of the nearest collapsible |
|
||||
| **`withinCollapsible`** | Determine when you are within another collaspible | |
|
||||
| Property | Description |
|
||||
| ------------------------- | ------------------------------------------------------------------------------------------------------------ | --- |
|
||||
| **`isCollapsed`** | State of the collapsible. `true` if open, `false` if collapsed |
|
||||
| **`isVisible`** | If nested, determine if the nearest collapsible is visible. `true` if no parent is closed, `false` otherwise |
|
||||
| **`toggle`** | Toggles the state of the nearest collapsible |
|
||||
| **`isWithinCollapsible`** | Determine when you are within another collaspible | |
|
||||
|
||||
**Example:**
|
||||
|
||||
@@ -654,10 +654,11 @@ import React from 'react'
|
||||
import { useCollapsible } from 'payload/components/utilities'
|
||||
|
||||
const CustomComponent: React.FC = () => {
|
||||
const { collapsed, toggle } = useCollapsible()
|
||||
const { isCollapsed, toggle } = useCollapsible()
|
||||
|
||||
return (
|
||||
<div>
|
||||
<p className="field-type">I am {collapsed ? 'closed' : 'open'}</p>
|
||||
<p className="field-type">I am {isCollapsed ? 'closed' : 'open'}</p>
|
||||
<button onClick={toggle} type="button">
|
||||
Toggle
|
||||
</button>
|
||||
|
||||
@@ -13,23 +13,25 @@ It's often best practice to write your Collections in separate files and then im
|
||||
|
||||
## Options
|
||||
|
||||
| Option | Description |
|
||||
| ----------------- | ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------ |
|
||||
| **`slug`** \* | Unique, URL-friendly string that will act as an identifier for this Collection. |
|
||||
| **`fields`** \* | Array of field types that will determine the structure and functionality of the data stored within this Collection. [Click here](/docs/fields/overview) for a full list of field types as well as how to configure them. |
|
||||
| **`labels`** | Singular and plural labels for use in identifying this Collection throughout Payload. Auto-generated from slug if not defined. |
|
||||
| **`admin`** | Admin-specific configuration. See below for [more detail](#admin-options). |
|
||||
| **`hooks`** | Entry points to "tie in" to Collection actions at specific points. [More](/docs/hooks/overview#collection-hooks) |
|
||||
| **`access`** | Provide access control functions to define exactly who should be able to do what with Documents in this Collection. [More](/docs/access-control/overview/#collections) |
|
||||
| **`auth`** | Specify options if you would like this Collection to feature authentication. For more, consult the [Authentication](/docs/authentication/config) documentation. |
|
||||
| **`upload`** | Specify options if you would like this Collection to support file uploads. For more, consult the [Uploads](/docs/upload/overview) documentation. |
|
||||
| **`timestamps`** | Set to false to disable documents' automatically generated `createdAt` and `updatedAt` timestamps. |
|
||||
| **`versions`** | Set to true to enable default options, or configure with object properties. [More](/docs/versions/overview#collection-config) |
|
||||
| **`endpoints`** | Add custom routes to the REST API. Set to `false` to disable routes. [More](/docs/rest-api/overview#custom-endpoints) |
|
||||
| **`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. |
|
||||
| **`custom`** | Extension point for adding custom data (e.g. for plugins) |
|
||||
| Option | Description |
|
||||
|------------------------|--------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------|
|
||||
| **`slug`** \* | Unique, URL-friendly string that will act as an identifier for this Collection. |
|
||||
| **`fields`** \* | Array of field types that will determine the structure and functionality of the data stored within this Collection. [Click here](/docs/fields/overview) for a full list of field types as well as how to configure them. |
|
||||
| **`labels`** | Singular and plural labels for use in identifying this Collection throughout Payload. Auto-generated from slug if not defined. |
|
||||
| **`admin`** | Admin-specific configuration. See below for [more detail](#admin-options). |
|
||||
| **`hooks`** | Entry points to "tie in" to Collection actions at specific points. [More](/docs/hooks/overview#collection-hooks) |
|
||||
| **`access`** | Provide access control functions to define exactly who should be able to do what with Documents in this Collection. [More](/docs/access-control/overview/#collections) |
|
||||
| **`auth`** | Specify options if you would like this Collection to feature authentication. For more, consult the [Authentication](/docs/authentication/config) documentation. |
|
||||
| **`upload`** | Specify options if you would like this Collection to support file uploads. For more, consult the [Uploads](/docs/upload/overview) documentation. |
|
||||
| **`timestamps`** | Set to false to disable documents' automatically generated `createdAt` and `updatedAt` timestamps. |
|
||||
| **`versions`** | Set to true to enable default options, or configure with object properties. [More](/docs/versions/overview#collection-config) |
|
||||
| **`endpoints`** | Add custom routes to the REST API. Set to `false` to disable routes. [More](/docs/rest-api/overview#custom-endpoints) |
|
||||
| **`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. |
|
||||
| **`disableDuplicate`** | When true, do not show the "Duplicate" button while editing documents within this collection and prevent `duplicate` from all APIs. |
|
||||
| **`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. |
|
||||
| **`dbName`** | Custom table or collection name depending on the database adapter. Auto-generated from slug if not defined.
|
||||
| **`custom`** | Extension point for adding custom data (e.g. for plugins) |
|
||||
|
||||
_\* An asterisk denotes that a property is required._
|
||||
|
||||
@@ -75,7 +77,6 @@ property on a collection's config.
|
||||
| `useAsTitle` | Specify a top-level field to use for a document title throughout the Admin panel. If no field is defined, the ID of the document is used as the title. |
|
||||
| `description` | Text or React component to display below the Collection label in the List view to give editors more information. |
|
||||
| `defaultColumns` | Array of field names that correspond to which columns to show by default in this collection's List view. |
|
||||
| `disableDuplicate ` | Disables the "Duplicate" button while editing documents within this collection. |
|
||||
| `hideAPIURL` | Hides the "API URL" meta field while editing documents within this collection. |
|
||||
| `enableRichTextLink` | The [Rich Text](/docs/fields/rich-text) field features a `Link` element which allows for users to automatically reference related documents within their rich text. Set to `true` by default. |
|
||||
| `enableRichTextRelationship` | The [Rich Text](/docs/fields/rich-text) field features a `Relationship` element which allows for users to automatically reference related documents within their rich text. Set to `true` by default. |
|
||||
|
||||
@@ -26,6 +26,7 @@ As with Collection configs, it's often best practice to write your Globals in se
|
||||
| **`graphQL.name`** | Text used in schema generation. Auto-generated from slug if not defined. |
|
||||
| **`typescript`** | An object with property `interface` as the text used in schema generation. Auto-generated from slug if not defined. |
|
||||
| **`custom`** | Extension point for adding custom data (e.g. for plugins) |
|
||||
| **`dbName`** | Custom table or collection name for this global depending on the database adapter. Auto-generated from slug if not defined.
|
||||
|
||||
_\* An asterisk denotes that a property is required._
|
||||
|
||||
|
||||
@@ -38,12 +38,18 @@ export default buildConfig({
|
||||
|
||||
### Options
|
||||
|
||||
| Option | Description |
|
||||
| -------------- | -------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
|
||||
| `pool` | [Pool connection options](https://orm.drizzle.team/docs/quick-postgresql/node-postgres) that will be passed to Drizzle and `node-postgres`. |
|
||||
| `push` | Disable Drizzle's [`db push`](https://orm.drizzle.team/kit-docs/overview#prototyping-with-db-push) in development mode. By default, `push` is enabled for development mode only. |
|
||||
| `migrationDir` | Customize the directory that migrations are stored. |
|
||||
| Option | Description |
|
||||
|-----------------------|-------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------|
|
||||
| `pool` \* | [Pool connection options](https://orm.drizzle.team/docs/quick-postgresql/node-postgres) that will be passed to Drizzle and `node-postgres`. |
|
||||
| `push` | Disable Drizzle's [`db push`](https://orm.drizzle.team/kit-docs/overview#prototyping-with-db-push) in development mode. By default, `push` is enabled for development mode only. |
|
||||
| `migrationDir` | Customize the directory that migrations are stored. |
|
||||
| `logger` | The instance of the logger to be passed to drizzle. By default Payload's will be used. |
|
||||
| `schemaName` | A string for the postgres schema to use, defaults to 'public'. |
|
||||
| `localesSuffix` | A string appended to the end of table names for storing localized fields. Default is '_locales'. |
|
||||
| `relationshipsSuffix` | A string appended to the end of table names for storing relationships. Default is '_rels'. |
|
||||
| `versionsSuffix` | A string appended to the end of table names for storing versions. Defaults to '_v'. |
|
||||
|
||||
|
||||
|
||||
### Access to Drizzle
|
||||
|
||||
|
||||
@@ -45,6 +45,7 @@ keywords: array, fields, config, configuration, documentation, Content Managemen
|
||||
| **`admin`** | Admin-specific configuration. See below for [more detail](#admin-config). |
|
||||
| **`custom`** | Extension point for adding custom data (e.g. for plugins) |
|
||||
| **`interfaceName`** | Create a top level, reusable [Typescript interface](/docs/typescript/generating-types#custom-field-interfaces) & [GraphQL type](/docs/graphql/graphql-schema#custom-field-schemas). |
|
||||
| **`dbName`** | Custom table name for the field when using SQL database adapter ([Postgres](/docs/database/postgres)). Auto-generated from name if not defined. |
|
||||
|
||||
_\* An asterisk denotes that a property is required._
|
||||
|
||||
|
||||
@@ -80,6 +80,7 @@ Blocks are defined as separate configs of their own.
|
||||
| **`imageAltText`** | Customize this block's image thumbnail alt text. |
|
||||
| **`interfaceName`** | Create a top level, reusable [Typescript interface](/docs/typescript/generating-types#custom-field-interfaces) & [GraphQL type](/docs/graphql/graphql-schema#custom-field-schemas). |
|
||||
| **`graphQL.singularName`** | Text to use for the GraphQL schema name. Auto-generated from slug if not defined. NOTE: this is set for deprecation, prefer `interfaceName`. |
|
||||
| **`dbName`** | Custom table name for this block type when using SQL database adapter ([Postgres](/docs/database/postgres)). Auto-generated from slug if not defined.
|
||||
| **`custom`** | Extension point for adding custom data (e.g. for plugins) |
|
||||
|
||||
#### Auto-generated data per block
|
||||
|
||||
@@ -4,7 +4,7 @@ label: JSON
|
||||
order: 50
|
||||
desc: The JSON field type will store any string in the Database. Learn how to use JSON fields, see examples and options.
|
||||
|
||||
keywords: json, fields, config, configuration, documentation, Content Management System, cms, headless, javascript, node, react, express
|
||||
keywords: json, jsonSchema, schema, validation, fields, config, configuration, documentation, Content Management System, cms, headless, javascript, node, react, express
|
||||
---
|
||||
|
||||
<Banner>
|
||||
@@ -30,6 +30,7 @@ This field uses the `monaco-react` editor syntax highlighting.
|
||||
| **`unique`** | Enforce that each entry in the Collection has a unique value for this field. |
|
||||
| **`index`** | Build an [index](/docs/database/overview) for this field to produce faster queries. Set this field to `true` if your users will perform queries on this field's data often. |
|
||||
| **`validate`** | Provide a custom validation function that will be executed on both the Admin panel and the backend. [More](/docs/fields/overview#validation) |
|
||||
| **`jsonSchema`** | Provide a JSON schema that will be used for validation. [JSON schemas](https://json-schema.org/learn/getting-started-step-by-step)
|
||||
| **`saveToJWT`** | If this field is top-level and nested in a config supporting [Authentication](/docs/authentication/config), include its data in the user JWT. |
|
||||
| **`hooks`** | Provide field-based hooks to control logic for this field. [More](/docs/fields/overview#field-level-hooks) |
|
||||
| **`access`** | Provide field-based access control to denote what users can see and do with this field's data. [More](/docs/fields/overview#field-level-access-control) |
|
||||
@@ -52,7 +53,7 @@ In addition to the default [field admin config](/docs/fields/overview#admin-conf
|
||||
|
||||
### Example
|
||||
|
||||
`collections/ExampleCollection.ts
|
||||
`collections/ExampleCollection.ts`
|
||||
|
||||
```ts
|
||||
import { CollectionConfig } from 'payload/types'
|
||||
@@ -68,3 +69,67 @@ export const ExampleCollection: CollectionConfig = {
|
||||
],
|
||||
}
|
||||
```
|
||||
### JSON Schema Validation
|
||||
|
||||
Payload JSON fields fully support the [JSON schema](https://json-schema.org/) standard. By providing a schema in your field config, the editor will be guided in the admin UI, getting typeahead for properties and their formats automatically. When the document is saved, the default validation will prevent saving any invalid data in the field according to the schema in your config.
|
||||
|
||||
If you only provide a URL to a schema, Payload will fetch the desired schema if it is publicly available. If not, it is recommended to add the schema directly to your config or import it from another file so that it can be implemented consistently in your project.
|
||||
|
||||
|
||||
#### Local JSON Schema
|
||||
|
||||
`collections/ExampleCollection.ts`
|
||||
|
||||
```ts
|
||||
import { CollectionConfig } from 'payload/types'
|
||||
|
||||
export const ExampleCollection: CollectionConfig = {
|
||||
slug: 'example-collection',
|
||||
fields: [
|
||||
{
|
||||
name: 'customerJSON', // required
|
||||
type: 'json', // required
|
||||
jsonSchema: {
|
||||
uri: 'a://b/foo.json', // required
|
||||
fileMatch: ['a://b/foo.json'], // required
|
||||
schema: {
|
||||
type: 'object',
|
||||
properties: {
|
||||
foo: {
|
||||
enum: ['bar', 'foobar'],
|
||||
}
|
||||
},
|
||||
},
|
||||
},
|
||||
|
||||
},
|
||||
],
|
||||
}
|
||||
// {"foo": "bar"} or {"foo": "foobar"} - ok
|
||||
// Attempting to create {"foo": "not-bar"} will throw an error
|
||||
```
|
||||
|
||||
#### Remote JSON Schema
|
||||
|
||||
`collections/ExampleCollection.ts`
|
||||
|
||||
```ts
|
||||
import { CollectionConfig } from 'payload/types'
|
||||
|
||||
export const ExampleCollection: CollectionConfig = {
|
||||
slug: 'example-collection',
|
||||
fields: [
|
||||
{
|
||||
name: 'customerJSON', // required
|
||||
type: 'json', // required
|
||||
jsonSchema: {
|
||||
uri: 'https://example.com/customer.schema.json', // required
|
||||
fileMatch: ['https://example.com/customer.schema.json'], // required
|
||||
},
|
||||
},
|
||||
],
|
||||
}
|
||||
// If 'https://example.com/customer.schema.json' has a JSON schema
|
||||
// {"foo": "bar"} or {"foo": "foobar"} - ok
|
||||
// Attempting to create {"foo": "not-bar"} will throw an error
|
||||
```
|
||||
|
||||
@@ -20,27 +20,27 @@ keywords: number, fields, config, configuration, documentation, Content Manageme
|
||||
|
||||
### Config
|
||||
|
||||
| Option | Description |
|
||||
| ------------------ | --------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
|
||||
| **`name`** \* | To be used as the property name when stored and retrieved from the database. [More](/docs/fields/overview#field-names) |
|
||||
| **`label`** | Text used as a field label in the Admin panel or an object with keys for each language. |
|
||||
| **`min`** | Minimum value accepted. Used in the default `validation` function. |
|
||||
| **`max`** | Maximum value accepted. Used in the default `validation` function. |
|
||||
| **`hasMany`** | Makes this field an ordered array of numbers instead of just a single number. |
|
||||
| **`minRows`** | Minimum number of numbers in the numbers array, if `hasMany` is set to true. |
|
||||
| **`maxRows`** | Maximum number of numbers in the numbers array, if `hasMany` is set to true. |
|
||||
| **`unique`** | Enforce that each entry in the Collection has a unique value for this field. |
|
||||
| **`index`** | Build an [index](/docs/database/overview) for this field to produce faster queries. Set this field to `true` if your users will perform queries on this field's data often. |
|
||||
| **`validate`** | Provide a custom validation function that will be executed on both the Admin panel and the backend. [More](/docs/fields/overview#validation) |
|
||||
| **`saveToJWT`** | If this field is top-level and nested in a config supporting [Authentication](/docs/authentication/config), include its data in the user JWT. |
|
||||
| **`hooks`** | Provide field-based hooks to control logic for this field. [More](/docs/fields/overview#field-level-hooks) |
|
||||
| **`access`** | Provide field-based access control to denote what users can see and do with this field's data. [More](/docs/fields/overview#field-level-access-control) |
|
||||
| **`hidden`** | Restrict this field's visibility from all APIs entirely. Will still be saved to the database, but will not appear in any API or the Admin panel. |
|
||||
| **`defaultValue`** | Provide data to be used for this field's default value. [More](/docs/fields/overview#default-values) |
|
||||
| **`localized`** | Enable localization for this field. Requires [localization to be enabled](/docs/configuration/localization) in the Base config. |
|
||||
| **`required`** | Require this field to have a value. |
|
||||
| **`admin`** | Admin-specific configuration. See below for [more detail](#admin-config). |
|
||||
| **`custom`** | Extension point for adding custom data (e.g. for plugins) |
|
||||
| Option | Description |
|
||||
|--------------------|--------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------|
|
||||
| **`name`** \* | To be used as the property name when stored and retrieved from the database. [More](/docs/fields/overview#field-names) |
|
||||
| **`label`** | Text used as a field label in the Admin panel or an object with keys for each language. |
|
||||
| **`min`** | Minimum value accepted. Used in the default `validation` function. |
|
||||
| **`max`** | Maximum value accepted. Used in the default `validation` function. |
|
||||
| **`hasMany`** | Makes this field an ordered array of numbers instead of just a single number. |
|
||||
| **`minRows`** | Minimum number of numbers in the numbers array, if `hasMany` is set to true. |
|
||||
| **`maxRows`** | Maximum number of numbers in the numbers array, if `hasMany` is set to true. |
|
||||
| **`unique`** | Enforce that each entry in the Collection has a unique value for this field. |
|
||||
| **`index`** | Build an [index](/docs/database/overview) for this field to produce faster queries. Set this field to `true` if your users will perform queries on this field's data often. |
|
||||
| **`validate`** | Provide a custom validation function that will be executed on both the Admin panel and the backend. [More](/docs/fields/overview#validation) |
|
||||
| **`saveToJWT`** | If this field is top-level and nested in a config supporting [Authentication](/docs/authentication/config), include its data in the user JWT. |
|
||||
| **`hooks`** | Provide field-based hooks to control logic for this field. [More](/docs/fields/overview#field-level-hooks) |
|
||||
| **`access`** | Provide field-based access control to denote what users can see and do with this field's data. [More](/docs/fields/overview#field-level-access-control) |
|
||||
| **`hidden`** | Restrict this field's visibility from all APIs entirely. Will still be saved to the database, but will not appear in any API or the Admin panel. |
|
||||
| **`defaultValue`** | Provide data to be used for this field's default value. [More](/docs/fields/overview#default-values) |
|
||||
| **`localized`** | Enable localization for this field. Requires [localization to be enabled](/docs/configuration/localization) in the Base config. |
|
||||
| **`required`** | Require this field to have a value. |
|
||||
| **`admin`** | Admin-specific configuration. See below for [more detail](#admin-config). |
|
||||
| **`custom`** | Extension point for adding custom data (e.g. for plugins) |
|
||||
|
||||
_\* An asterisk denotes that a property is required._
|
||||
|
||||
|
||||
@@ -36,6 +36,7 @@ keywords: radio, fields, config, configuration, documentation, Content Managemen
|
||||
| **`required`** | Require this field to have a value. |
|
||||
| **`admin`** | Admin-specific configuration. See below for [more detail](#admin-config). |
|
||||
| **`custom`** | Extension point for adding custom data (e.g. for plugins) |
|
||||
| **`enumName`** | Custom enum name for this field when using SQL database adapter ([Postgres](/docs/database/postgres)). Auto-generated from name if not defined.
|
||||
|
||||
_\* An asterisk denotes that a property is required._
|
||||
|
||||
|
||||
@@ -38,6 +38,8 @@ keywords: select, multi-select, fields, config, configuration, documentation, Co
|
||||
| **`required`** | Require this field to have a value. |
|
||||
| **`admin`** | Admin-specific configuration. See the [default field admin config](/docs/fields/overview#admin-config) for more details. |
|
||||
| **`custom`** | Extension point for adding custom data (e.g. for plugins) |
|
||||
| **`enumName`** | Custom enum name for this field when using SQL database adapter ([Postgres](/docs/database/postgres)). Auto-generated from name if not defined. |
|
||||
| **`dbName`** | Custom table name (if `hasMany` set to `true`) for this field when using SQL database adapter ([Postgres](/docs/database/postgres)). Auto-generated from name if not defined. |
|
||||
|
||||
_\* An asterisk denotes that a property is required._
|
||||
|
||||
|
||||
@@ -42,11 +42,12 @@ export const PublicUser: CollectionConfig = {
|
||||
|
||||
**Payload will automatically open up the following queries:**
|
||||
|
||||
| Query Name | Operation |
|
||||
| ------------------ | ------------------- |
|
||||
| **`PublicUser`** | `findByID` |
|
||||
| **`PublicUsers`** | `find` |
|
||||
| **`mePublicUser`** | `me` auth operation |
|
||||
| Query Name | Operation |
|
||||
| ------------------ | ------------------- |
|
||||
| **`PublicUser`** | `findByID` |
|
||||
| **`PublicUsers`** | `find` |
|
||||
| **`countPublicUsers`** | `count` |
|
||||
| **`mePublicUser`** | `me` auth operation |
|
||||
|
||||
**And the following mutations:**
|
||||
|
||||
|
||||
@@ -221,9 +221,13 @@ user-friendly.
|
||||
|
||||
### beforeDuplicate
|
||||
|
||||
The `beforeDuplicate` field hook is only called when duplicating a document. It may be used when documents having the
|
||||
exact same properties may cause issue. This gives you a way to avoid duplicate names on `unique`, `required` fields or
|
||||
to unset values by returning `null`. This is called immediately after `defaultValue` and before validation occurs.
|
||||
The `beforeDuplicate` field hook is called on each locale (when using localization), when duplicating a document. It may be used when documents having the
|
||||
exact same properties may cause issue. This gives you a way to avoid duplicate names on `unique`, `required` fields or when external systems expect non-repeating values on documents.
|
||||
|
||||
This hook gets called after `beforeChange` hooks are called and before the document is saved to the database.
|
||||
|
||||
By Default, unique and required text fields Payload will append "- Copy" to the original document value. The default is not added if your field has its own, you must return non-unique values from your beforeDuplicate hook to avoid errors or enable the `disableDuplicate` option on the collection.
|
||||
Here is an example of a number field with a hook that increments the number to avoid unique constraint errors when duplicating a document:
|
||||
|
||||
```ts
|
||||
import { Field } from 'payload/types'
|
||||
|
||||
@@ -8,7 +8,7 @@ keywords: live preview, frontend, react, next.js, vue, nuxt.js, svelte, hook, us
|
||||
|
||||
While using Live Preview, the Admin panel emits a new `window.postMessage` event every time a change is made to the document. Your front-end application can listen for these events and re-render accordingly.
|
||||
|
||||
Wiring your front-end into Live Preview is easy. If your front-end application is built with React or Next.js, use the [`useLivePreview`](#react) React hook that Payload provides. In the future, all other major frameworks like Vue, Svelte, etc will be officially supported. If you are using any of these frameworks today, you can still integrate with Live Preview yourself using the underlying tooling that Payload provides. See [building your own hook](#building-your-own-hook) for more information.
|
||||
Wiring your front-end into Live Preview is easy. If your front-end application is built with React, Next.js, Vue or Nuxt.js, use the `useLivePreview` hook that Payload provides. In the future, all other major frameworks like Svelte will be officially supported. If you are using any of these frameworks today, you can still integrate with Live Preview yourself using the underlying tooling that Payload provides. See [building your own hook](#building-your-own-hook) for more information.
|
||||
|
||||
By default, all hooks accept the following args:
|
||||
|
||||
@@ -36,6 +36,10 @@ And return the following values:
|
||||
For example, `data?.relatedPosts?.[0]?.title`.
|
||||
</Banner>
|
||||
|
||||
<Banner type="info">
|
||||
It is important that the `depth` argument matches exactly with the depth of your initial page request. The depth property is used to populated relationships and uploads beyond their IDs. See [Depth](../getting-started/concepts#depth) for more information.
|
||||
</Banner>
|
||||
|
||||
### React
|
||||
|
||||
If your front-end application is built with React or Next.js, you can use the `useLivePreview` hook that Payload provides.
|
||||
@@ -71,11 +75,40 @@ export const PageClient: React.FC<{
|
||||
}
|
||||
```
|
||||
|
||||
<Banner type="info">
|
||||
If is important that the `depth` argument matches exactly with the depth of your initial page
|
||||
request. The depth property is used to populated relationships and uploads beyond their IDs. See
|
||||
[Depth](../getting-started/concepts#depth) for more information.
|
||||
</Banner>
|
||||
### Vue
|
||||
|
||||
If your front-end application is built with Vue 3 or Nuxt 3, you can use the `useLivePreview` composable that Payload provides.
|
||||
|
||||
First, install the `@payloadcms/live-preview-vue` package:
|
||||
|
||||
```bash
|
||||
npm install @payloadcms/live-preview-vue
|
||||
```
|
||||
|
||||
Then, use the `useLivePreview` hook in your Vue component:
|
||||
|
||||
```vue
|
||||
<script setup lang="ts">
|
||||
import type { PageData } from '~/types';
|
||||
import { defineProps } from 'vue';
|
||||
import { useLivePreview } from '@payloadcms/live-preview-vue';
|
||||
|
||||
// Fetch the initial data on the parent component or using async state
|
||||
const props = defineProps<{ initialData: PageData }>();
|
||||
|
||||
// The hook will take over from here and keep the preview in sync with the changes you make.
|
||||
// The `data` property will contain the live data of the document only when viewed from the Preview view of the Admin UI.
|
||||
const { data } = useLivePreview<PageData>({
|
||||
initialData: props.initialData,
|
||||
serverURL: "<PAYLOAD_SERVER_URL>",
|
||||
depth: 2,
|
||||
});
|
||||
</script>
|
||||
|
||||
<template>
|
||||
<h1>{{ data.title }}</h1>
|
||||
</template>
|
||||
```
|
||||
|
||||
## Building your own hook
|
||||
|
||||
|
||||
@@ -164,6 +164,22 @@ const result = await payload.findByID({
|
||||
})
|
||||
```
|
||||
|
||||
#### Count
|
||||
|
||||
```js
|
||||
// Result will be an object with:
|
||||
// {
|
||||
// totalDocs: 10, // count of the documents satisfies query
|
||||
// }
|
||||
const result = await payload.count({
|
||||
collection: 'posts', // required
|
||||
locale: 'en',
|
||||
where: {}, // pass a `where` query here
|
||||
user: dummyUser,
|
||||
overrideAccess: false,
|
||||
})
|
||||
```
|
||||
|
||||
#### Update by ID
|
||||
|
||||
```js
|
||||
|
||||
@@ -90,6 +90,19 @@ Note: Collection slugs must be formatted in kebab-case
|
||||
},
|
||||
},
|
||||
},
|
||||
{
|
||||
operation: "Count",
|
||||
method: "GET",
|
||||
path: "/api/{collection-slug}/count",
|
||||
description: "Count the documents",
|
||||
example: {
|
||||
slug: "count",
|
||||
req: true,
|
||||
res: {
|
||||
totalDocs: 10
|
||||
},
|
||||
},
|
||||
},
|
||||
{
|
||||
operation: "Create",
|
||||
method: "POST",
|
||||
|
||||
@@ -138,7 +138,7 @@ import { CallToAction } from '../blocks/CallToAction'
|
||||
Here's an overview of all the included features:
|
||||
|
||||
| Feature Name | Included by default | Description |
|
||||
| ------------------------------ | ------------------- | -------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
|
||||
|--------------------------------|---------------------|----------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------|
|
||||
| **`BoldTextFeature`** | Yes | Handles the bold text format |
|
||||
| **`ItalicTextFeature`** | Yes | Handles the italic text format |
|
||||
| **`UnderlineTextFeature`** | Yes | Handles the underline text format |
|
||||
@@ -157,7 +157,8 @@ Here's an overview of all the included features:
|
||||
| **`RelationshipFeature`** | Yes | Allows you to create block-level (not inline) relationships to other documents |
|
||||
| **`BlockQuoteFeature`** | Yes | Allows you to create block-level quotes |
|
||||
| **`UploadFeature`** | Yes | Allows you to create block-level upload nodes - this supports all kinds of uploads, not just images |
|
||||
| **`BlocksFeature`** | No | Allows you to use Payload's [Blocks Field](/docs/fields/blocks) directly inside your editor. In the feature props, you can specify the allowed blocks - just like in the Blocks field. |
|
||||
| **`HorizontalRuleFeature`** | Yes | Horizontal rules / separators. Basically displays an <hr> element |
|
||||
| **`BlocksFeature`** | No | Allows you to use Payload's [Blocks Field](/docs/fields/blocks) directly inside your editor. In the feature props, you can specify the allowed blocks - just like in the Blocks field. |
|
||||
| **`TreeViewFeature`** | No | Adds a debug box under the editor, which allows you to see the current editor state live, the dom, as well as time travel. Very useful for debugging |
|
||||
|
||||
## Creating your own, custom Feature
|
||||
@@ -173,7 +174,7 @@ Next, take a look at the [features we've already built](https://github.com/paylo
|
||||
Lexical saves data in JSON, but can also generate its HTML representation via two main methods:
|
||||
|
||||
1. **Outputting HTML from the Collection:** Create a new field in your collection to convert saved JSON content to HTML. Payload generates and outputs the HTML for use in your frontend.
|
||||
2. **Generating HTML on the Frontend:** Convert JSON to HTML on-demand, either in your frontend or elsewhere.
|
||||
2. **Generating HTML on any server** Convert JSON to HTML on-demand on the server.
|
||||
|
||||
The editor comes with built-in HTML serializers, simplifying the process of converting JSON to HTML.
|
||||
|
||||
@@ -207,7 +208,7 @@ const Pages: CollectionConfig = {
|
||||
|
||||
The `lexicalHTML()` function creates a new field that automatically converts the referenced lexical richText field into HTML through an afterRead hook.
|
||||
|
||||
#### Generating HTML in the Frontend:
|
||||
#### Generating HTML anywhere on the server:
|
||||
|
||||
If you wish to convert JSON to HTML ad-hoc, use this code snippet:
|
||||
|
||||
@@ -234,6 +235,19 @@ This method employs `convertLexicalToHTML` from `@payloadcms/richtext-lexical`,
|
||||
|
||||
Because every `Feature` is able to provide html converters, and because the `htmlFeature` can modify those or provide their own, we need to consolidate them with the default html Converters using the `consolidateHTMLConverters` function.
|
||||
|
||||
#### CSS
|
||||
|
||||
Payload's lexical HTML converter does not generate CSS for you, but it does add classes to the generated HTML. You can use these classes to style the HTML in your frontend.
|
||||
|
||||
Here is some "base" CSS you can use to ensure that nested lists render correctly:
|
||||
|
||||
```css
|
||||
/* Base CSS for Lexical HTML */
|
||||
.nestedListItem, .list-check {
|
||||
list-style-type: none;
|
||||
}
|
||||
```
|
||||
|
||||
#### Creating your own HTML Converter
|
||||
|
||||
HTML Converters are typed as `HTMLConverter`, which contains the node type it should handle, and a function that accepts the serialized node from the lexical editor, and outputs the HTML string. Here's the HTML Converter of the Upload node as an example:
|
||||
|
||||
@@ -167,7 +167,9 @@ Specifying custom `Type`s let you extend your custom elements by adding addition
|
||||
`collections/ExampleCollection.ts`
|
||||
|
||||
```ts
|
||||
import { CollectionConfig } from 'payload/types'
|
||||
import type { CollectionConfig } from 'payload/types'
|
||||
|
||||
import { slateEditor } from '@payloadcms/richtext-slate'
|
||||
|
||||
export const ExampleCollection: CollectionConfig = {
|
||||
slug: 'example-collection',
|
||||
@@ -181,57 +183,59 @@ export const ExampleCollection: CollectionConfig = {
|
||||
},
|
||||
],
|
||||
required: true,
|
||||
admin: {
|
||||
elements: [
|
||||
'h2',
|
||||
'h3',
|
||||
'h4',
|
||||
'link',
|
||||
'blockquote',
|
||||
{
|
||||
name: 'cta',
|
||||
Button: CustomCallToActionButton,
|
||||
Element: CustomCallToActionElement,
|
||||
plugins: [
|
||||
// any plugins that are required by this element go here
|
||||
],
|
||||
},
|
||||
],
|
||||
leaves: [
|
||||
'bold',
|
||||
'italic',
|
||||
{
|
||||
name: 'highlight',
|
||||
Button: CustomHighlightButton,
|
||||
Leaf: CustomHighlightLeaf,
|
||||
plugins: [
|
||||
// any plugins that are required by this leaf go here
|
||||
],
|
||||
},
|
||||
],
|
||||
link: {
|
||||
// Inject your own fields into the Link element
|
||||
fields: [
|
||||
editor: slateEditor({
|
||||
admin: {
|
||||
elements: [
|
||||
'h2',
|
||||
'h3',
|
||||
'h4',
|
||||
'link',
|
||||
'blockquote',
|
||||
{
|
||||
name: 'rel',
|
||||
label: 'Rel Attribute',
|
||||
type: 'select',
|
||||
hasMany: true,
|
||||
options: ['noopener', 'noreferrer', 'nofollow'],
|
||||
},
|
||||
],
|
||||
},
|
||||
upload: {
|
||||
collections: {
|
||||
media: {
|
||||
fields: [
|
||||
// any fields that you would like to save
|
||||
// on an upload element in the `media` collection
|
||||
name: 'cta',
|
||||
Button: CustomCallToActionButton,
|
||||
Element: CustomCallToActionElement,
|
||||
plugins: [
|
||||
// any plugins that are required by this element go here
|
||||
],
|
||||
},
|
||||
],
|
||||
leaves: [
|
||||
'bold',
|
||||
'italic',
|
||||
{
|
||||
name: 'highlight',
|
||||
Button: CustomHighlightButton,
|
||||
Leaf: CustomHighlightLeaf,
|
||||
plugins: [
|
||||
// any plugins that are required by this leaf go here
|
||||
],
|
||||
},
|
||||
],
|
||||
link: {
|
||||
// Inject your own fields into the Link element
|
||||
fields: [
|
||||
{
|
||||
name: 'rel',
|
||||
label: 'Rel Attribute',
|
||||
type: 'select',
|
||||
hasMany: true,
|
||||
options: ['noopener', 'noreferrer', 'nofollow'],
|
||||
},
|
||||
],
|
||||
},
|
||||
upload: {
|
||||
collections: {
|
||||
media: {
|
||||
fields: [
|
||||
// any fields that you would like to save
|
||||
// on an upload element in the `media` collection
|
||||
],
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
}),
|
||||
},
|
||||
],
|
||||
}
|
||||
|
||||
@@ -40,21 +40,22 @@ Every Payload Collection can opt-in to supporting Uploads by specifying the `upl
|
||||
|
||||
### Collection Upload Options
|
||||
|
||||
| Option | Description |
|
||||
| --------------------------- | ---------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
|
||||
| **`staticURL`** \* | The URL path to use to access your uploads. Relative path like `/media` will be served by payload. Full path like `https://example.com/media` needs to be served by another web server. |
|
||||
| **`staticDir`** \* | The folder directory to use to store media in. Can be either an absolute path or relative to the directory that contains your config. |
|
||||
| **`adminThumbnail`** | Set the way that the Admin panel will display thumbnails for this Collection. [More](#admin-thumbnails) |
|
||||
| **`crop`** | Set to `false` to disable the cropping tool in the Admin panel. Crop is enabled by default. [More](#crop-and-focal-point-selector) |
|
||||
| **`disableLocalStorage`** | Completely disable uploading files to disk locally. [More](#disabling-local-upload-storage) |
|
||||
| **`focalPoint`** | Set to `false` to disable the focal point selection tool in the Admin panel. The focal point selector is only available when `imageSizes` or `resizeOptions` are defined. [More](#crop-and-focal-point-selector) |
|
||||
| **`formatOptions`** | An object with `format` and `options` that are used with the Sharp image library to format the upload file. [More](https://sharp.pixelplumbing.com/api-output#toformat) |
|
||||
| **`handlers`** | Array of Express request handlers to execute before the built-in Payload static middleware executes. |
|
||||
| **`imageSizes`** | If specified, image uploads will be automatically resized in accordance to these image sizes. [More](#image-sizes) |
|
||||
| **`mimeTypes`** | Restrict mimeTypes in the file picker. Array of valid mimetypes or mimetype wildcards [More](#mimetypes) |
|
||||
| **`staticOptions`** | Set options for `express.static` to use while serving your static files. [More](http://expressjs.com/en/resources/middleware/serve-static.html) |
|
||||
| **`resizeOptions`** | An object passed to the the Sharp image library to resize the uploaded file. [More](https://sharp.pixelplumbing.com/api-resize) |
|
||||
| **`filesRequiredOnCreate`** | Mandate file data on creation, default is true. |
|
||||
| Option | Description |
|
||||
| ------------------------------ | ---------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
|
||||
| **`staticURL`** \* | The URL path to use to access your uploads. Relative path like `/media` will be served by payload. Full path like `https://example.com/media` needs to be served by another web server. |
|
||||
| **`staticDir`** \* | The folder directory to use to store media in. Can be either an absolute path or relative to the directory that contains your config. |
|
||||
| **`adminThumbnail`** | Set the way that the Admin panel will display thumbnails for this Collection. [More](#admin-thumbnails) |
|
||||
| **`crop`** | Set to `false` to disable the cropping tool in the Admin panel. Crop is enabled by default. [More](#crop-and-focal-point-selector) |
|
||||
| **`disableLocalStorage`** | Completely disable uploading files to disk locally. [More](#disabling-local-upload-storage) |
|
||||
| **`externalFileHeaderFilter`** | Accepts existing headers and can filter/modify them. |
|
||||
| **`focalPoint`** | Set to `false` to disable the focal point selection tool in the Admin panel. The focal point selector is only available when `imageSizes` or `resizeOptions` are defined. [More](#crop-and-focal-point-selector) |
|
||||
| **`formatOptions`** | An object with `format` and `options` that are used with the Sharp image library to format the upload file. [More](https://sharp.pixelplumbing.com/api-output#toformat) |
|
||||
| **`handlers`** | Array of Express request handlers to execute before the built-in Payload static middleware executes. |
|
||||
| **`imageSizes`** | If specified, image uploads will be automatically resized in accordance to these image sizes. [More](#image-sizes) |
|
||||
| **`mimeTypes`** | Restrict mimeTypes in the file picker. Array of valid mimetypes or mimetype wildcards [More](#mimetypes) |
|
||||
| **`staticOptions`** | Set options for `express.static` to use while serving your static files. [More](http://expressjs.com/en/resources/middleware/serve-static.html) |
|
||||
| **`resizeOptions`** | An object passed to the the Sharp image library to resize the uploaded file. [More](https://sharp.pixelplumbing.com/api-resize) |
|
||||
| **`filesRequiredOnCreate`** | Mandate file data on creation, default is true. |
|
||||
|
||||
_An asterisk denotes that a property above is required._
|
||||
|
||||
|
||||
@@ -1 +0,0 @@
|
||||
export default {}
|
||||
@@ -1,5 +1,5 @@
|
||||
/** @type {import('jest').Config} */
|
||||
const customJestConfig = {
|
||||
const baseJestConfig = {
|
||||
extensionsToTreatAsEsm: ['.ts', '.tsx'],
|
||||
setupFilesAfterEnv: ['<rootDir>/test/jest.setup.ts'],
|
||||
moduleNameMapper: {
|
||||
@@ -8,9 +8,8 @@ const customJestConfig = {
|
||||
'<rootDir>/test/helpers/mocks/fileMock.js',
|
||||
'^(\\.{1,2}/.*)\\.js$': '$1',
|
||||
},
|
||||
reporters: ['default', ['github-actions', { silent: false }], 'summary'],
|
||||
testEnvironment: 'node',
|
||||
testMatch: ['<rootDir>/packages/payload/src/**/*.spec.ts', '<rootDir>/test/**/*int.spec.ts'],
|
||||
testMatch: ['<rootDir>/packages/*/src/**/*.spec.ts'],
|
||||
testTimeout: 90000,
|
||||
transform: {
|
||||
'^.+\\.(t|j)sx?$': ['@swc/jest'],
|
||||
@@ -19,7 +18,7 @@ const customJestConfig = {
|
||||
}
|
||||
|
||||
if (process.env.CI) {
|
||||
customJestConfig.reporters = [['github-actions', { silent: false }], 'summary']
|
||||
baseJestConfig.reporters = [['github-actions', { silent: false }], 'summary']
|
||||
}
|
||||
|
||||
export default customJestConfig
|
||||
export default baseJestConfig
|
||||
|
||||
@@ -10,6 +10,12 @@ const withBundleAnalyzer = bundleAnalyzer({
|
||||
export default withBundleAnalyzer(
|
||||
withPayload({
|
||||
reactStrictMode: false,
|
||||
eslint: {
|
||||
ignoreDuringBuilds: true,
|
||||
},
|
||||
typescript: {
|
||||
ignoreBuildErrors: true,
|
||||
},
|
||||
async redirects() {
|
||||
return [
|
||||
{
|
||||
|
||||
55
package.json
55
package.json
@@ -1,6 +1,6 @@
|
||||
{
|
||||
"name": "payload-monorepo",
|
||||
"version": "3.0.0-alpha.49",
|
||||
"version": "3.0.0-beta.15",
|
||||
"private": true,
|
||||
"type": "module",
|
||||
"workspaces:": [
|
||||
@@ -18,6 +18,7 @@
|
||||
"build:create-payload-app": "turbo build --filter create-payload-app",
|
||||
"build:db-mongodb": "turbo build --filter db-mongodb",
|
||||
"build:db-postgres": "turbo build --filter db-postgres",
|
||||
"build:email-nodemailer": "turbo build --filter email-nodemailer",
|
||||
"build:eslint-config-payload": "turbo build --filter eslint-config-payload",
|
||||
"build:graphql": "turbo build --filter graphql",
|
||||
"build:live-preview": "turbo build --filter live-preview",
|
||||
@@ -35,7 +36,7 @@
|
||||
"build:plugin-stripe": "turbo build --filter plugin-stripe",
|
||||
"build:richtext-lexical": "turbo build --filter richtext-lexical",
|
||||
"build:richtext-slate": "turbo build --filter richtext-slate",
|
||||
"build:tests": "pnpm --filter test run typecheck",
|
||||
"build:tests": "pnpm --filter payload-test-suite run typecheck",
|
||||
"build:translations": "turbo build --filter translations",
|
||||
"build:ui": "turbo build --filter ui",
|
||||
"clean": "turbo clean",
|
||||
@@ -46,10 +47,10 @@
|
||||
"devsafe": "rimraf .next && pnpm dev",
|
||||
"dev:generate-graphql-schema": "cross-env NODE_OPTIONS=--no-deprecation tsx ./test/generateGraphQLSchema.ts",
|
||||
"dev:generate-types": "cross-env NODE_OPTIONS=--no-deprecation tsx ./test/generateTypes.ts",
|
||||
"dev:postgres": "pnpm --filter payload run dev:postgres",
|
||||
"dev:postgres": "cross-env NODE_OPTIONS=--no-deprecation PAYLOAD_DATABASE=postgres node ./test/dev.js",
|
||||
"docker:restart": "pnpm docker:stop --remove-orphans && pnpm docker:start",
|
||||
"docker:start": "docker-compose -f packages/plugin-cloud-storage/docker-compose.yml up -d",
|
||||
"docker:stop": "docker-compose -f packages/plugin-cloud-storage/docker-compose.yml down",
|
||||
"docker:start": "docker compose -f packages/plugin-cloud-storage/docker-compose.yml up -d",
|
||||
"docker:stop": "docker compose -f packages/plugin-cloud-storage/docker-compose.yml down",
|
||||
"fix": "eslint \"packages/**/*.ts\" --fix",
|
||||
"generate:types": "PAYLOAD_CONFIG_PATH=./test/_community/config.ts node --no-deprecation ./packages/payload/bin.js generate:types",
|
||||
"lint": "eslint \"packages/**/*.ts\"",
|
||||
@@ -57,7 +58,6 @@
|
||||
"prepare": "husky install",
|
||||
"pretest": "pnpm build",
|
||||
"reinstall": "pnpm clean:all && pnpm install",
|
||||
"script:list-packages": "tsx ./scripts/list-packages.ts",
|
||||
"script:pack": "tsx scripts/pack-all-to-dest.ts",
|
||||
"release:alpha": "tsx ./scripts/release.ts --bump prerelease --tag alpha",
|
||||
"release:beta": "tsx ./scripts/release.ts --bump prerelease --tag beta",
|
||||
@@ -66,8 +66,9 @@
|
||||
"test:e2e": "cross-env NODE_OPTIONS=--no-deprecation NODE_NO_WARNINGS=1 tsx ./test/runE2E.ts",
|
||||
"test:e2e:debug": "cross-env NODE_OPTIONS=--no-deprecation NODE_NO_WARNINGS=1 PWDEBUG=1 DISABLE_LOGGING=true playwright test",
|
||||
"test:e2e:headed": "cross-env NODE_OPTIONS=--no-deprecation NODE_NO_WARNINGS=1 DISABLE_LOGGING=true playwright test --headed",
|
||||
"test:int:postgres": "cross-env NODE_OPTIONS=--no-deprecation NODE_NO_WARNINGS=1 PAYLOAD_DATABASE=postgres DISABLE_LOGGING=true jest --forceExit --detectOpenHandles",
|
||||
"test:int": "cross-env NODE_OPTIONS=--no-deprecation NODE_NO_WARNINGS=1 DISABLE_LOGGING=true jest --forceExit --detectOpenHandles",
|
||||
"test:int:postgres": "cross-env NODE_OPTIONS=--no-deprecation NODE_NO_WARNINGS=1 PAYLOAD_DATABASE=postgres DISABLE_LOGGING=true jest --forceExit --detectOpenHandles --config=test/jest.config.js --runInBand",
|
||||
"test:int": "cross-env NODE_OPTIONS=--no-deprecation NODE_NO_WARNINGS=1 DISABLE_LOGGING=true jest --forceExit --detectOpenHandles --config=test/jest.config.js --runInBand",
|
||||
"test:unit": "cross-env NODE_OPTIONS=--no-deprecation NODE_NO_WARNINGS=1 DISABLE_LOGGING=true jest --forceExit --detectOpenHandles --config=jest.config.js --runInBand",
|
||||
"translateNewKeys": "pnpm --filter payload run translateNewKeys"
|
||||
},
|
||||
"devDependencies": {
|
||||
@@ -76,7 +77,7 @@
|
||||
"@octokit/core": "^5.1.0",
|
||||
"@payloadcms/eslint-config": "workspace:*",
|
||||
"@payloadcms/live-preview-react": "workspace:*",
|
||||
"@playwright/test": "^1.42.1",
|
||||
"@playwright/test": "1.43.0",
|
||||
"@swc/cli": "^0.1.62",
|
||||
"@swc/jest": "0.2.36",
|
||||
"@testing-library/jest-dom": "6.4.2",
|
||||
@@ -88,13 +89,13 @@
|
||||
"@types/conventional-changelog-writer": "^4.0.10",
|
||||
"@types/fs-extra": "^11.0.2",
|
||||
"@types/jest": "29.5.12",
|
||||
"@types/minimist": "1.2.2",
|
||||
"@types/node": "20.11.28",
|
||||
"@types/minimist": "1.2.5",
|
||||
"@types/node": "20.12.5",
|
||||
"@types/prompts": "^2.4.5",
|
||||
"@types/qs": "6.9.7",
|
||||
"@types/react": "18.2.15",
|
||||
"@types/qs": "6.9.14",
|
||||
"@types/react": "18.2.74",
|
||||
"@types/semver": "^7.5.3",
|
||||
"@types/shelljs": "0.8.12",
|
||||
"@types/shelljs": "0.8.15",
|
||||
"add-stream": "^1.0.0",
|
||||
"chalk": "^4.1.2",
|
||||
"comment-json": "^4.2.3",
|
||||
@@ -127,14 +128,15 @@
|
||||
"lint-staged": "^14.0.1",
|
||||
"minimist": "1.2.8",
|
||||
"mongodb-memory-server": "^9.0",
|
||||
"next": "14.2.0-canary.22",
|
||||
"next": "^14.3.0-canary.7",
|
||||
"node-mocks-http": "^1.14.1",
|
||||
"nodemon": "3.0.3",
|
||||
"open": "^10.1.0",
|
||||
"p-limit": "^5.0.0",
|
||||
"pino": "8.15.0",
|
||||
"pino-pretty": "10.2.0",
|
||||
"playwright": "^1.42.1",
|
||||
"playwright-core": "^1.42.1",
|
||||
"playwright": "1.43.0",
|
||||
"playwright-core": "1.43.0",
|
||||
"prettier": "^3.0.3",
|
||||
"prompts": "2.4.2",
|
||||
"qs": "6.11.2",
|
||||
@@ -145,7 +147,7 @@
|
||||
"semver": "^7.5.4",
|
||||
"sharp": "0.32.6",
|
||||
"shelljs": "0.8.5",
|
||||
"simple-git": "^3.20.0",
|
||||
"simple-git": "^3.24.0",
|
||||
"slash": "3.0.0",
|
||||
"slate": "0.91.4",
|
||||
"swc-plugin-transform-remove-imports": "^1.12.1",
|
||||
@@ -153,18 +155,17 @@
|
||||
"tempy": "^1.0.1",
|
||||
"ts-node": "10.9.1",
|
||||
"tsx": "^4.7.1",
|
||||
"turbo": "^1.13.0",
|
||||
"typescript": "5.4.2",
|
||||
"turbo": "^1.13.2",
|
||||
"typescript": "5.4.4",
|
||||
"uuid": "^9.0.1",
|
||||
"yocto-queue": "^1.0.0"
|
||||
},
|
||||
"peerDependencies": {
|
||||
"react": "18.2.0",
|
||||
"react-router-dom": "5.3.4"
|
||||
"react": "18.2.0"
|
||||
},
|
||||
"engines": {
|
||||
"node": ">=18.17.0",
|
||||
"pnpm": ">=8"
|
||||
"node": ">=18.20.2",
|
||||
"pnpm": "^8.15.7"
|
||||
},
|
||||
"lint-staged": {
|
||||
"*.{md,mdx,yml,json}": "prettier --write",
|
||||
@@ -175,6 +176,7 @@
|
||||
},
|
||||
"dependencies": {
|
||||
"@sentry/react": "^7.77.0",
|
||||
"ajv": "^8.12.0",
|
||||
"passport-strategy": "1.0.0"
|
||||
},
|
||||
"pnpm": {
|
||||
@@ -195,8 +197,7 @@
|
||||
"domexception": "4"
|
||||
},
|
||||
"patchedDependencies": {
|
||||
"playwright@1.42.1": "patches/playwright@1.42.1.patch"
|
||||
"playwright@1.43.0": "patches/playwright@1.43.0.patch"
|
||||
}
|
||||
},
|
||||
"packageManager": "pnpm@8.15.4+sha256.cea6d0bdf2de3a0549582da3983c70c92ffc577ff4410cbf190817ddc35137c2"
|
||||
}
|
||||
}
|
||||
|
||||
@@ -10,6 +10,6 @@
|
||||
}
|
||||
},
|
||||
"module": {
|
||||
"type": "commonjs"
|
||||
"type": "es6"
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,2 +1,4 @@
|
||||
#!/usr/bin/env node
|
||||
require('../dist/index.js')
|
||||
|
||||
import { main } from '../dist/index.js'
|
||||
main()
|
||||
|
||||
@@ -1,11 +1,32 @@
|
||||
import baseConfig from '../../jest.config.js'
|
||||
// import baseConfig from '../../jest.config.js'
|
||||
|
||||
/** @type {import('@jest/types').Config} */
|
||||
// /** @type {import('@jest/types').Config} */
|
||||
// const customJestConfig = {
|
||||
// ...baseConfig,
|
||||
// setupFilesAfterEnv: null,
|
||||
// testMatch: ['**/src/**/?(*.)+(spec|test|it-test).[tj]s?(x)'],
|
||||
// testTimeout: 20000,
|
||||
// }
|
||||
|
||||
// export default customJestConfig
|
||||
|
||||
/** @type {import('jest').Config} */
|
||||
const customJestConfig = {
|
||||
...baseConfig,
|
||||
setupFilesAfterEnv: null,
|
||||
testMatch: ['**/src/**/?(*.)+(spec|test|it-test).[tj]s?(x)'],
|
||||
testTimeout: 20000,
|
||||
extensionsToTreatAsEsm: ['.ts', '.tsx'],
|
||||
// setupFilesAfterEnv: ['<rootDir>/jest.setup.ts'],
|
||||
moduleNameMapper: {
|
||||
'\\.(css|scss)$': '<rootDir>/helpers/mocks/emptyModule.js',
|
||||
'\\.(jpg|jpeg|png|gif|eot|otf|webp|svg|ttf|woff|woff2|mp4|webm|wav|mp3|m4a|aac|oga)$':
|
||||
'<rootDir>/test/helpers/mocks/fileMock.js',
|
||||
'^(\\.{1,2}/.*)\\.js$': '$1',
|
||||
},
|
||||
testEnvironment: 'node',
|
||||
testMatch: ['<rootDir>/**/*spec.ts'],
|
||||
testTimeout: 90000,
|
||||
transform: {
|
||||
'^.+\\.(t|j)sx?$': ['@swc/jest'],
|
||||
},
|
||||
verbose: true,
|
||||
}
|
||||
|
||||
export default customJestConfig
|
||||
|
||||
@@ -1,19 +1,25 @@
|
||||
{
|
||||
"name": "create-payload-app",
|
||||
"version": "1.0.0",
|
||||
"version": "3.0.0-beta.15",
|
||||
"license": "MIT",
|
||||
"type": "module",
|
||||
"homepage": "https://payloadcms.com",
|
||||
"bin": {
|
||||
"create-payload-app": "bin/cli.js"
|
||||
},
|
||||
"repository": {
|
||||
"type": "git",
|
||||
"url": "https://github.com/payloadcms/payload.git",
|
||||
"directory": "packages/create-payload-app"
|
||||
},
|
||||
"scripts": {
|
||||
"build": "pnpm copyfiles && pnpm typecheck && pnpm build:swc",
|
||||
"build": "pnpm pack-template-files && pnpm typecheck && pnpm build:swc",
|
||||
"typecheck": "tsc",
|
||||
"copyfiles": "copyfiles -u 2 \"../../app/(payload)/**\" \"dist\"",
|
||||
"pack-template-files": "tsx src/scripts/pack-template-files.ts",
|
||||
"build:swc": "swc ./src -d ./dist --config-file .swcrc",
|
||||
"clean": "rimraf {dist,*.tsbuildinfo}",
|
||||
"test": "jest",
|
||||
"prepublishOnly": "pnpm test && pnpm clean && pnpm build"
|
||||
"prepublishOnly": "pnpm clean && pnpm build"
|
||||
},
|
||||
"files": [
|
||||
"package.json",
|
||||
@@ -21,6 +27,7 @@
|
||||
"bin"
|
||||
],
|
||||
"dependencies": {
|
||||
"@clack/prompts": "^0.7.0",
|
||||
"@sindresorhus/slugify": "^1.1.0",
|
||||
"arg": "^5.0.0",
|
||||
"chalk": "^4.1.0",
|
||||
@@ -28,14 +35,11 @@
|
||||
"comment-json": "^4.2.3",
|
||||
"degit": "^2.8.4",
|
||||
"detect-package-manager": "^3.0.1",
|
||||
"esprima": "^4.0.1",
|
||||
"esprima-next": "^6.0.3",
|
||||
"execa": "^5.0.0",
|
||||
"figures": "^3.2.0",
|
||||
"figures": "^6.1.0",
|
||||
"fs-extra": "^9.0.1",
|
||||
"globby": "11.1.0",
|
||||
"handlebars": "^4.7.7",
|
||||
"ora": "^5.1.0",
|
||||
"prompts": "^2.4.2",
|
||||
"terminal-link": "^2.1.1"
|
||||
},
|
||||
"devDependencies": {
|
||||
@@ -44,8 +48,7 @@
|
||||
"@types/esprima": "^4.0.6",
|
||||
"@types/fs-extra": "^9.0.12",
|
||||
"@types/jest": "^27.0.3",
|
||||
"@types/node": "^16.6.2",
|
||||
"@types/prompts": "^2.4.1"
|
||||
"@types/node": "20.12.5"
|
||||
},
|
||||
"exports": {
|
||||
"./commands": {
|
||||
|
||||
@@ -1,8 +1,12 @@
|
||||
import { Main } from './main.js'
|
||||
import { error } from './utils/log.js'
|
||||
|
||||
async function main(): Promise<void> {
|
||||
await new Main().init()
|
||||
export async function main(): Promise<void> {
|
||||
try {
|
||||
await new Main().init()
|
||||
} catch (e) {
|
||||
if (e instanceof Error) {
|
||||
error(e.message)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
main().catch((e) => error(`An error has occurred: ${e instanceof Error ? e.message : e}`))
|
||||
|
||||
@@ -1,5 +1,9 @@
|
||||
import fse from 'fs-extra'
|
||||
import globby from 'globby'
|
||||
import { fileURLToPath } from 'node:url'
|
||||
import path from 'path'
|
||||
const filename = fileURLToPath(import.meta.url)
|
||||
const dirname = path.dirname(filename)
|
||||
|
||||
import type { DbDetails } from '../types.js'
|
||||
|
||||
@@ -9,19 +13,55 @@ import { dbReplacements } from './packages.js'
|
||||
/** Update payload config with necessary imports and adapters */
|
||||
export async function configurePayloadConfig(args: {
|
||||
dbDetails: DbDetails | undefined
|
||||
projectDir: string
|
||||
projectDirOrConfigPath: { payloadConfigPath: string } | { projectDir: string }
|
||||
}): Promise<void> {
|
||||
if (!args.dbDetails) {
|
||||
return
|
||||
}
|
||||
|
||||
// Update package.json
|
||||
const packageJsonPath =
|
||||
'projectDir' in args.projectDirOrConfigPath &&
|
||||
path.resolve(args.projectDirOrConfigPath.projectDir, 'package.json')
|
||||
|
||||
if (packageJsonPath && fse.existsSync(packageJsonPath)) {
|
||||
try {
|
||||
const packageObj = await fse.readJson(packageJsonPath)
|
||||
|
||||
const dbPackage = dbReplacements[args.dbDetails.type]
|
||||
|
||||
// Delete all other db adapters
|
||||
Object.values(dbReplacements).forEach((p) => {
|
||||
if (p.packageName !== dbPackage.packageName) {
|
||||
delete packageObj.dependencies[p.packageName]
|
||||
}
|
||||
})
|
||||
|
||||
// Set version of db adapter to match payload version
|
||||
packageObj.dependencies[dbPackage.packageName] = packageObj.dependencies['payload']
|
||||
|
||||
await fse.writeJson(packageJsonPath, packageObj, { spaces: 2 })
|
||||
} catch (err: unknown) {
|
||||
warning(`Unable to configure Payload in package.json`)
|
||||
warning(err instanceof Error ? err.message : '')
|
||||
}
|
||||
}
|
||||
|
||||
try {
|
||||
const payloadConfigPath = (
|
||||
await globby('**/payload.config.ts', { absolute: true, cwd: args.projectDir })
|
||||
)?.[0]
|
||||
let payloadConfigPath: string | undefined
|
||||
if (!('payloadConfigPath' in args.projectDirOrConfigPath)) {
|
||||
payloadConfigPath = (
|
||||
await globby('**/payload.config.ts', {
|
||||
absolute: true,
|
||||
cwd: args.projectDirOrConfigPath.projectDir,
|
||||
})
|
||||
)?.[0]
|
||||
} else {
|
||||
payloadConfigPath = args.projectDirOrConfigPath.payloadConfigPath
|
||||
}
|
||||
|
||||
if (!payloadConfigPath) {
|
||||
warning('Unable to update payload.config.ts with plugins')
|
||||
warning('Unable to update payload.config.ts with plugins. Could not find payload.config.ts.')
|
||||
return
|
||||
}
|
||||
|
||||
@@ -59,6 +99,8 @@ export async function configurePayloadConfig(args: {
|
||||
|
||||
fse.writeFileSync(payloadConfigPath, configLines.join('\n'))
|
||||
} catch (err: unknown) {
|
||||
warning('Unable to update payload.config.ts with plugins')
|
||||
warning(
|
||||
`Unable to update payload.config.ts with plugins: ${err instanceof Error ? err.message : ''}`,
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
@@ -5,6 +5,7 @@ import { createProject } from './create-project.js'
|
||||
import { fileURLToPath } from 'node:url'
|
||||
import { dbReplacements } from './packages.js'
|
||||
import { getValidTemplates } from './templates.js'
|
||||
import globby from 'globby'
|
||||
|
||||
const filename = fileURLToPath(import.meta.url)
|
||||
const dirname = path.dirname(filename)
|
||||
@@ -104,12 +105,17 @@ describe('createProject', () => {
|
||||
Object.keys(packageJson.dependencies).filter((n) => n.startsWith('@payloadcms/db-')),
|
||||
).toHaveLength(1)
|
||||
|
||||
let payloadConfigPath = path.resolve(projectDir, 'payload.config.ts')
|
||||
const payloadConfigPath = (
|
||||
await globby('**/payload.config.ts', {
|
||||
absolute: true,
|
||||
cwd: projectDir,
|
||||
})
|
||||
)?.[0]
|
||||
|
||||
// Website and ecommerce templates have payload.config.ts in src/payload
|
||||
if (!fse.existsSync(payloadConfigPath)) {
|
||||
payloadConfigPath = path.resolve(projectDir, 'src/payload/payload.config.ts')
|
||||
if (!payloadConfigPath) {
|
||||
throw new Error(`Could not find payload.config.ts inside ${projectDir}`)
|
||||
}
|
||||
|
||||
const content = fse.readFileSync(payloadConfigPath, 'utf-8')
|
||||
|
||||
// Check payload.config.ts
|
||||
@@ -122,9 +128,4 @@ describe('createProject', () => {
|
||||
})
|
||||
})
|
||||
})
|
||||
|
||||
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
|
||||
})
|
||||
})
|
||||
|
||||
@@ -1,14 +1,14 @@
|
||||
import * as p from '@clack/prompts'
|
||||
import chalk from 'chalk'
|
||||
import degit from 'degit'
|
||||
import execa from 'execa'
|
||||
import fse from 'fs-extra'
|
||||
import { fileURLToPath } from 'node:url'
|
||||
import ora from 'ora'
|
||||
import path from 'path'
|
||||
|
||||
import type { CliArgs, DbDetails, PackageManager, ProjectTemplate } from '../types.js'
|
||||
|
||||
import { debug, error, success, warning } from '../utils/log.js'
|
||||
import { debug, error, warning } from '../utils/log.js'
|
||||
import { configurePayloadConfig } from './configure-payload-config.js'
|
||||
|
||||
const filename = fileURLToPath(import.meta.url)
|
||||
@@ -60,14 +60,12 @@ export async function createProject(args: {
|
||||
const { cliArgs, dbDetails, packageManager, projectDir, projectName, template } = args
|
||||
|
||||
if (cliArgs['--dry-run']) {
|
||||
console.log(`\n Dry run: Creating project in ${chalk.green(projectDir)}\n`)
|
||||
debug(`Dry run: Creating project in ${chalk.green(projectDir)}`)
|
||||
return
|
||||
}
|
||||
|
||||
await createOrFindProjectDir(projectDir)
|
||||
|
||||
console.log(`\n Creating project in ${chalk.green(projectDir)}\n`)
|
||||
|
||||
if (cliArgs['--local-template']) {
|
||||
// Copy template from local path. For development purposes.
|
||||
const localTemplate = path.resolve(
|
||||
@@ -86,10 +84,12 @@ export async function createProject(args: {
|
||||
await emitter.clone(projectDir)
|
||||
}
|
||||
|
||||
const spinner = ora('Checking latest Payload version...').start()
|
||||
const spinner = p.spinner()
|
||||
spinner.start('Checking latest Payload version...')
|
||||
|
||||
await updatePackageJSON({ projectDir, projectName })
|
||||
await configurePayloadConfig({ dbDetails, projectDir })
|
||||
spinner.message('Configuring Payload...')
|
||||
await configurePayloadConfig({ dbDetails, projectDirOrConfigPath: { projectDir } })
|
||||
|
||||
// Remove yarn.lock file. This is only desired in Payload Cloud.
|
||||
const lockPath = path.resolve(projectDir, 'yarn.lock')
|
||||
@@ -98,18 +98,15 @@ export async function createProject(args: {
|
||||
}
|
||||
|
||||
if (!cliArgs['--no-deps']) {
|
||||
spinner.text = 'Installing dependencies...'
|
||||
spinner.message('Installing dependencies...')
|
||||
const result = await installDeps({ cliArgs, packageManager, projectDir })
|
||||
spinner.stop()
|
||||
spinner.clear()
|
||||
if (result) {
|
||||
success('Dependencies installed')
|
||||
spinner.stop('Successfully installed Payload and dependencies')
|
||||
} else {
|
||||
error('Error installing dependencies')
|
||||
spinner.stop('Error installing dependencies', 1)
|
||||
}
|
||||
} else {
|
||||
spinner.stop()
|
||||
spinner.clear()
|
||||
spinner.stop('Dependency installation skipped')
|
||||
}
|
||||
}
|
||||
|
||||
@@ -124,6 +121,6 @@ export async function updatePackageJSON(args: {
|
||||
packageObj.name = projectName
|
||||
await fse.writeJson(packageJsonPath, packageObj, { spaces: 2 })
|
||||
} catch (err: unknown) {
|
||||
warning('Unable to update name in package.json')
|
||||
warning(`Unable to update name in package.json. ${err instanceof Error ? err.message : ''}`)
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
import type { CompilerOptions } from 'typescript'
|
||||
|
||||
import chalk from 'chalk'
|
||||
import * as p from '@clack/prompts'
|
||||
import { parse, stringify } from 'comment-json'
|
||||
import execa from 'execa'
|
||||
import fs from 'fs'
|
||||
@@ -8,6 +8,7 @@ import fse from 'fs-extra'
|
||||
import globby from 'globby'
|
||||
import path from 'path'
|
||||
import { promisify } from 'util'
|
||||
|
||||
const readFile = promisify(fs.readFile)
|
||||
const writeFile = promisify(fs.writeFile)
|
||||
|
||||
@@ -16,114 +17,174 @@ const dirname = path.dirname(filename)
|
||||
|
||||
import { fileURLToPath } from 'node:url'
|
||||
|
||||
import type { CliArgs, PackageManager } from '../types.js'
|
||||
import type { CliArgs, DbType, PackageManager } from '../types.js'
|
||||
|
||||
import { copyRecursiveSync } from '../utils/copy-recursive-sync.js'
|
||||
import { error, info, debug as origDebug, success, warning } from '../utils/log.js'
|
||||
import { debug as origDebug, warning } from '../utils/log.js'
|
||||
import { moveMessage } from '../utils/messages.js'
|
||||
import { wrapNextConfig } from './wrap-next-config.js'
|
||||
|
||||
type InitNextArgs = Pick<CliArgs, '--debug'> & {
|
||||
dbType: DbType
|
||||
nextAppDetails?: NextAppDetails
|
||||
packageManager: PackageManager
|
||||
projectDir?: string
|
||||
projectDir: string
|
||||
useDistFiles?: boolean
|
||||
}
|
||||
type InitNextResult = { reason?: string; success: boolean; userAppDir?: string }
|
||||
|
||||
type NextConfigType = 'cjs' | 'esm'
|
||||
|
||||
type InitNextResult =
|
||||
| {
|
||||
isSrcDir: boolean
|
||||
nextAppDir: string
|
||||
payloadConfigPath: string
|
||||
success: true
|
||||
}
|
||||
| { isSrcDir: boolean; nextAppDir?: string; reason: string; success: false }
|
||||
|
||||
export async function initNext(args: InitNextArgs): Promise<InitNextResult> {
|
||||
const { packageManager, projectDir } = args
|
||||
const templateResult = await applyPayloadTemplateFiles(args)
|
||||
if (!templateResult.success) return templateResult
|
||||
const { dbType: dbType, packageManager, projectDir } = args
|
||||
|
||||
const { success: installSuccess } = await installDeps(projectDir, packageManager)
|
||||
if (!installSuccess) {
|
||||
return { ...templateResult, reason: 'Failed to install dependencies', success: false }
|
||||
const nextAppDetails = args.nextAppDetails || (await getNextAppDetails(projectDir))
|
||||
|
||||
if (!nextAppDetails.nextAppDir) {
|
||||
warning(`Could not find app directory in ${projectDir}, creating...`)
|
||||
const createdAppDir = path.resolve(projectDir, nextAppDetails.isSrcDir ? 'src/app' : 'app')
|
||||
fse.mkdirSync(createdAppDir, { recursive: true })
|
||||
nextAppDetails.nextAppDir = createdAppDir
|
||||
}
|
||||
|
||||
// Create or find payload.config.ts
|
||||
const createConfigResult = findOrCreatePayloadConfig(projectDir)
|
||||
if (!createConfigResult.success) {
|
||||
return { ...templateResult, ...createConfigResult }
|
||||
const { hasTopLevelLayout, isSrcDir, nextAppDir, nextConfigType } = nextAppDetails
|
||||
|
||||
if (!nextConfigType) {
|
||||
return {
|
||||
isSrcDir,
|
||||
nextAppDir,
|
||||
reason: `Could not determine Next Config type in ${projectDir}. Possibly try renaming next.config.js to next.config.cjs or next.config.mjs.`,
|
||||
success: false,
|
||||
}
|
||||
}
|
||||
|
||||
if (hasTopLevelLayout) {
|
||||
// Output directions for user to move all files from app to top-level directory named `(app)`
|
||||
p.log.warn(moveMessage({ nextAppDir, projectDir }))
|
||||
return {
|
||||
isSrcDir,
|
||||
nextAppDir,
|
||||
reason: 'Found existing layout.tsx in app directory',
|
||||
success: false,
|
||||
}
|
||||
}
|
||||
|
||||
const installSpinner = p.spinner()
|
||||
installSpinner.start('Installing Payload and dependencies...')
|
||||
|
||||
const configurationResult = installAndConfigurePayload({
|
||||
...args,
|
||||
nextAppDetails,
|
||||
nextConfigType,
|
||||
useDistFiles: true, // Requires running 'pnpm pack-template-files' in cpa
|
||||
})
|
||||
|
||||
if (configurationResult.success === false) {
|
||||
installSpinner.stop(configurationResult.reason, 1)
|
||||
return { ...configurationResult, isSrcDir, success: false }
|
||||
}
|
||||
|
||||
const { success: installSuccess } = await installDeps(projectDir, packageManager, dbType)
|
||||
if (!installSuccess) {
|
||||
installSpinner.stop('Failed to install dependencies', 1)
|
||||
return {
|
||||
...configurationResult,
|
||||
isSrcDir,
|
||||
reason: 'Failed to install dependencies',
|
||||
success: false,
|
||||
}
|
||||
}
|
||||
|
||||
// Add `@payload-config` to tsconfig.json `paths`
|
||||
await addPayloadConfigToTsConfig(projectDir)
|
||||
|
||||
// Output directions for user to update next.config.js
|
||||
const withPayloadMessage = `
|
||||
|
||||
${chalk.bold(`Wrap your existing next.config.js with the withPayload function. Here is an example:`)}
|
||||
|
||||
import withPayload from '@payloadcms/next/withPayload'
|
||||
|
||||
const nextConfig = {
|
||||
// Your Next.js config here
|
||||
}
|
||||
|
||||
export default withPayload(nextConfig)
|
||||
|
||||
`
|
||||
|
||||
console.log(withPayloadMessage)
|
||||
|
||||
return templateResult
|
||||
await addPayloadConfigToTsConfig(projectDir, isSrcDir)
|
||||
installSpinner.stop('Successfully installed Payload and dependencies')
|
||||
return { ...configurationResult, isSrcDir, nextAppDir, success: true }
|
||||
}
|
||||
|
||||
async function addPayloadConfigToTsConfig(projectDir: string) {
|
||||
async function addPayloadConfigToTsConfig(projectDir: string, isSrcDir: boolean) {
|
||||
const tsConfigPath = path.resolve(projectDir, 'tsconfig.json')
|
||||
|
||||
// Check if tsconfig.json exists
|
||||
if (!fs.existsSync(tsConfigPath)) {
|
||||
warning(`Could not find tsconfig.json to add @payload-config path.`)
|
||||
return
|
||||
}
|
||||
const userTsConfigContent = await readFile(tsConfigPath, {
|
||||
encoding: 'utf8',
|
||||
})
|
||||
const userTsConfig = parse(userTsConfigContent) as {
|
||||
compilerOptions?: CompilerOptions
|
||||
}
|
||||
|
||||
const hasBaseUrl =
|
||||
userTsConfig?.compilerOptions?.baseUrl && userTsConfig?.compilerOptions?.baseUrl !== '.'
|
||||
const baseUrl = hasBaseUrl ? userTsConfig?.compilerOptions?.baseUrl : './'
|
||||
|
||||
if (!userTsConfig.compilerOptions && !('extends' in userTsConfig)) {
|
||||
userTsConfig.compilerOptions = {}
|
||||
}
|
||||
|
||||
if (!userTsConfig.compilerOptions.paths?.['@payload-config']) {
|
||||
if (
|
||||
!userTsConfig.compilerOptions?.paths?.['@payload-config'] &&
|
||||
userTsConfig.compilerOptions?.paths
|
||||
) {
|
||||
userTsConfig.compilerOptions.paths = {
|
||||
...(userTsConfig.compilerOptions.paths || {}),
|
||||
'@payload-config': ['./payload.config.ts'],
|
||||
'@payload-config': [`${baseUrl}${isSrcDir ? 'src/' : ''}payload.config.ts`],
|
||||
}
|
||||
await writeFile(tsConfigPath, stringify(userTsConfig, null, 2), { encoding: 'utf8' })
|
||||
}
|
||||
}
|
||||
|
||||
async function applyPayloadTemplateFiles(args: InitNextArgs): Promise<InitNextResult> {
|
||||
const { '--debug': debug, projectDir, useDistFiles } = args
|
||||
function installAndConfigurePayload(
|
||||
args: InitNextArgs & {
|
||||
nextAppDetails: NextAppDetails
|
||||
nextConfigType: NextConfigType
|
||||
useDistFiles?: boolean
|
||||
},
|
||||
):
|
||||
| { payloadConfigPath: string; success: true }
|
||||
| { payloadConfigPath?: string; reason: string; success: false } {
|
||||
const {
|
||||
'--debug': debug,
|
||||
nextAppDetails: { isSrcDir, nextAppDir, nextConfigPath } = {},
|
||||
nextConfigType,
|
||||
projectDir,
|
||||
useDistFiles,
|
||||
} = args
|
||||
|
||||
info('Initializing Payload app in Next.js project', 1)
|
||||
if (!nextAppDir || !nextConfigPath) {
|
||||
return {
|
||||
reason: 'Could not find app directory or next.config.js',
|
||||
success: false,
|
||||
}
|
||||
}
|
||||
|
||||
const logDebug = (message: string) => {
|
||||
if (debug) origDebug(message)
|
||||
}
|
||||
|
||||
if (!fs.existsSync(projectDir)) {
|
||||
return { reason: `Could not find specified project directory at ${projectDir}`, success: false }
|
||||
}
|
||||
|
||||
// Next.js configs can be next.config.js, next.config.mjs, etc.
|
||||
const foundConfig = (await globby('next.config.*js', { absolute: true, cwd: projectDir }))?.[0]
|
||||
|
||||
if (!foundConfig) {
|
||||
throw new Error(`No next.config.js found at ${projectDir}`)
|
||||
}
|
||||
|
||||
const nextConfigPath = path.resolve(projectDir, foundConfig)
|
||||
if (!fs.existsSync(nextConfigPath)) {
|
||||
return {
|
||||
reason: `No next.config.js found at ${nextConfigPath}. Ensure you are in a Next.js project directory.`,
|
||||
reason: `Could not find specified project directory at ${projectDir}`,
|
||||
success: false,
|
||||
}
|
||||
} else {
|
||||
if (debug) logDebug(`Found Next config at ${nextConfigPath}`)
|
||||
}
|
||||
|
||||
const templateFilesPath =
|
||||
dirname.endsWith('dist') || useDistFiles
|
||||
? path.resolve(dirname, '../..', 'dist/app')
|
||||
: path.resolve(dirname, '../../../../app')
|
||||
? path.resolve(dirname, '../..', 'dist/template')
|
||||
: path.resolve(dirname, '../../../../templates/blank-3.0')
|
||||
|
||||
if (debug) logDebug(`Using template files from: ${templateFilesPath}`)
|
||||
logDebug(`Using template files from: ${templateFilesPath}`)
|
||||
|
||||
if (!fs.existsSync(templateFilesPath)) {
|
||||
return {
|
||||
@@ -131,38 +192,41 @@ async function applyPayloadTemplateFiles(args: InitNextArgs): Promise<InitNextRe
|
||||
success: false,
|
||||
}
|
||||
} else {
|
||||
if (debug) logDebug('Found template source files')
|
||||
logDebug('Found template source files')
|
||||
}
|
||||
|
||||
// src/app or app
|
||||
const userAppDir = (
|
||||
await globby(['**/app'], {
|
||||
absolute: true,
|
||||
cwd: projectDir,
|
||||
onlyDirectories: true,
|
||||
})
|
||||
)?.[0]
|
||||
logDebug(`Copying template files from ${templateFilesPath} to ${nextAppDir}`)
|
||||
|
||||
if (!fs.existsSync(userAppDir)) {
|
||||
return { reason: `Could not find user app directory inside ${projectDir}`, success: false }
|
||||
} else {
|
||||
logDebug(`Found user app directory: ${userAppDir}`)
|
||||
const templateSrcDir = path.resolve(templateFilesPath, isSrcDir ? '' : 'src')
|
||||
|
||||
logDebug(`templateSrcDir: ${templateSrcDir}`)
|
||||
logDebug(`nextAppDir: ${nextAppDir}`)
|
||||
logDebug(`projectDir: ${projectDir}`)
|
||||
logDebug(`nextConfigPath: ${nextConfigPath}`)
|
||||
logDebug(`payloadConfigPath: ${path.resolve(projectDir, 'payload.config.ts')}`)
|
||||
|
||||
logDebug(
|
||||
`isSrcDir: ${isSrcDir}. source: ${templateSrcDir}. dest: ${path.dirname(nextConfigPath)}`,
|
||||
)
|
||||
|
||||
// This is a little clunky and needs to account for isSrcDir
|
||||
copyRecursiveSync(templateSrcDir, path.dirname(nextConfigPath), debug)
|
||||
|
||||
// Wrap next.config.js with withPayload
|
||||
wrapNextConfig({ nextConfigPath, nextConfigType })
|
||||
|
||||
return {
|
||||
payloadConfigPath: path.resolve(nextAppDir, '../payload.config.ts'),
|
||||
success: true,
|
||||
}
|
||||
|
||||
logDebug(`Copying template files from ${templateFilesPath} to ${userAppDir}`)
|
||||
copyRecursiveSync(templateFilesPath, userAppDir, debug)
|
||||
success('Successfully initialized.')
|
||||
return { success: true, userAppDir }
|
||||
}
|
||||
|
||||
async function installDeps(projectDir: string, packageManager: PackageManager) {
|
||||
info(`Installing dependencies with ${packageManager}`, 1)
|
||||
const packagesToInstall = [
|
||||
'payload',
|
||||
'@payloadcms/db-mongodb',
|
||||
'@payloadcms/next',
|
||||
'@payloadcms/richtext-lexical',
|
||||
].map((pkg) => `${pkg}@alpha`)
|
||||
async function installDeps(projectDir: string, packageManager: PackageManager, dbType: DbType) {
|
||||
const packagesToInstall = ['payload', '@payloadcms/next', '@payloadcms/richtext-lexical'].map(
|
||||
(pkg) => `${pkg}@beta`,
|
||||
)
|
||||
|
||||
packagesToInstall.push(`@payloadcms/db-${dbType}@beta`)
|
||||
|
||||
let exitCode = 0
|
||||
switch (packageManager) {
|
||||
@@ -186,43 +250,69 @@ async function installDeps(projectDir: string, packageManager: PackageManager) {
|
||||
}
|
||||
}
|
||||
|
||||
if (exitCode !== 0) {
|
||||
error(`Failed to install dependencies with ${packageManager}`)
|
||||
} else {
|
||||
success(`Successfully installed dependencies`)
|
||||
}
|
||||
return { success: exitCode === 0 }
|
||||
}
|
||||
function findOrCreatePayloadConfig(projectDir: string) {
|
||||
const configPath = path.resolve(projectDir, 'payload.config.ts')
|
||||
if (fs.existsSync(configPath)) {
|
||||
return { message: 'Found existing payload.config.ts', success: true }
|
||||
} else {
|
||||
// Create default config
|
||||
// TODO: Pull this from templates
|
||||
const defaultConfig = `import path from "path";
|
||||
|
||||
import { mongooseAdapter } from "@payloadcms/db-mongodb"; // database-adapter-import
|
||||
import { lexicalEditor } from "@payloadcms/richtext-lexical"; // editor-import
|
||||
import { buildConfig } from "payload/config";
|
||||
|
||||
export default buildConfig({
|
||||
editor: slateEditor({}), // editor-config
|
||||
collections: [],
|
||||
secret: "asdfasdf",
|
||||
typescript: {
|
||||
outputFile: path.resolve(__dirname, "payload-types.ts"),
|
||||
},
|
||||
graphQL: {
|
||||
schemaOutputFile: path.resolve(__dirname, "generated-schema.graphql"),
|
||||
},
|
||||
db: mongooseAdapter({
|
||||
url: "mongodb://localhost:27017/next-payload-3",
|
||||
}),
|
||||
});
|
||||
`
|
||||
|
||||
fse.writeFileSync(configPath, defaultConfig)
|
||||
return { message: 'Created default payload.config.ts', success: true }
|
||||
}
|
||||
type NextAppDetails = {
|
||||
hasTopLevelLayout: boolean
|
||||
isSrcDir: boolean
|
||||
nextAppDir?: string
|
||||
nextConfigPath?: string
|
||||
nextConfigType?: NextConfigType
|
||||
}
|
||||
|
||||
export async function getNextAppDetails(projectDir: string): Promise<NextAppDetails> {
|
||||
const isSrcDir = fs.existsSync(path.resolve(projectDir, 'src'))
|
||||
|
||||
const nextConfigPath: string | undefined = (
|
||||
await globby('next.config.*js', { absolute: true, cwd: projectDir })
|
||||
)?.[0]
|
||||
if (!nextConfigPath || nextConfigPath.length === 0) {
|
||||
return {
|
||||
hasTopLevelLayout: false,
|
||||
isSrcDir,
|
||||
nextConfigPath: undefined,
|
||||
}
|
||||
}
|
||||
|
||||
let nextAppDir: string | undefined = (
|
||||
await globby(['**/app'], {
|
||||
absolute: true,
|
||||
cwd: projectDir,
|
||||
ignore: ['**/node_modules/**'],
|
||||
onlyDirectories: true,
|
||||
})
|
||||
)?.[0]
|
||||
|
||||
if (!nextAppDir || nextAppDir.length === 0) {
|
||||
nextAppDir = undefined
|
||||
}
|
||||
|
||||
const configType = await getProjectType(projectDir, nextConfigPath)
|
||||
|
||||
const hasTopLevelLayout = nextAppDir
|
||||
? fs.existsSync(path.resolve(nextAppDir, 'layout.tsx'))
|
||||
: false
|
||||
|
||||
return { hasTopLevelLayout, isSrcDir, nextAppDir, nextConfigPath, nextConfigType: configType }
|
||||
}
|
||||
|
||||
async function getProjectType(projectDir: string, nextConfigPath: string): Promise<'cjs' | 'esm'> {
|
||||
if (nextConfigPath.endsWith('.mjs')) {
|
||||
return 'esm'
|
||||
}
|
||||
if (nextConfigPath.endsWith('.cjs')) {
|
||||
return 'cjs'
|
||||
}
|
||||
|
||||
const packageObj = await fse.readJson(path.resolve(projectDir, 'package.json'))
|
||||
const packageJsonType = packageObj.type
|
||||
if (packageJsonType === 'module') {
|
||||
return 'esm'
|
||||
}
|
||||
if (packageJsonType === 'commonjs') {
|
||||
return 'cjs'
|
||||
}
|
||||
|
||||
return 'cjs'
|
||||
}
|
||||
|
||||
@@ -10,14 +10,18 @@ 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,', ' }),'],
|
||||
configReplacement: [
|
||||
' db: mongooseAdapter({',
|
||||
" url: process.env.DATABASE_URI || '',",
|
||||
' }),',
|
||||
],
|
||||
}
|
||||
|
||||
const postgresReplacement: DbAdapterReplacement = {
|
||||
configReplacement: [
|
||||
' db: postgresAdapter({',
|
||||
' pool: {',
|
||||
' connectionString: process.env.DATABASE_URI,',
|
||||
" connectionString: process.env.DATABASE_URI || '',",
|
||||
' },',
|
||||
' }),',
|
||||
],
|
||||
|
||||
@@ -1,25 +1,20 @@
|
||||
import prompts from 'prompts'
|
||||
import * as p from '@clack/prompts'
|
||||
import slugify from '@sindresorhus/slugify'
|
||||
|
||||
import type { CliArgs } from '../types.js'
|
||||
|
||||
export async function parseProjectName(args: CliArgs): Promise<string> {
|
||||
if (args['--init-next']) return '.'
|
||||
if (args['--name']) return args['--name']
|
||||
if (args._[0]) return args._[0]
|
||||
if (args['--name']) return slugify(args['--name'])
|
||||
if (args._[0]) return slugify(args._[0])
|
||||
|
||||
const response = await prompts(
|
||||
{
|
||||
name: 'value',
|
||||
type: 'text',
|
||||
message: 'Project name?',
|
||||
validate: (value: string) => !!value.length,
|
||||
const projectName = await p.text({
|
||||
message: 'Project name?',
|
||||
validate: (value) => {
|
||||
if (!value) return 'Please enter a project name.'
|
||||
},
|
||||
{
|
||||
onCancel: () => {
|
||||
process.exit(0)
|
||||
},
|
||||
},
|
||||
)
|
||||
|
||||
return response.value
|
||||
})
|
||||
if (p.isCancel(projectName)) {
|
||||
process.exit(0)
|
||||
}
|
||||
return slugify(projectName)
|
||||
}
|
||||
|
||||
@@ -1,11 +1,11 @@
|
||||
import prompts from 'prompts'
|
||||
import * as p from '@clack/prompts'
|
||||
|
||||
import type { CliArgs, ProjectTemplate } from '../types.js'
|
||||
|
||||
export async function parseTemplate(
|
||||
args: CliArgs,
|
||||
validTemplates: ProjectTemplate[],
|
||||
): Promise<ProjectTemplate> {
|
||||
): Promise<ProjectTemplate | undefined> {
|
||||
if (args['--template']) {
|
||||
const templateName = args['--template']
|
||||
const template = validTemplates.find((t) => t.name === templateName)
|
||||
@@ -13,29 +13,20 @@ export async function parseTemplate(
|
||||
return template
|
||||
}
|
||||
|
||||
const response = await prompts(
|
||||
{
|
||||
name: 'value',
|
||||
type: 'select',
|
||||
choices: validTemplates.map((p) => {
|
||||
return {
|
||||
description: p.description,
|
||||
title: p.name,
|
||||
value: p.name,
|
||||
}
|
||||
}),
|
||||
message: 'Choose project template',
|
||||
validate: (value: string) => !!value.length,
|
||||
},
|
||||
{
|
||||
onCancel: () => {
|
||||
process.exit(0)
|
||||
},
|
||||
},
|
||||
)
|
||||
const response = await p.select<{ label: string; value: string }[], string>({
|
||||
message: 'Choose project template',
|
||||
options: validTemplates.map((p) => {
|
||||
return {
|
||||
label: p.name,
|
||||
value: p.name,
|
||||
}
|
||||
}),
|
||||
})
|
||||
if (p.isCancel(response)) {
|
||||
process.exit(0)
|
||||
}
|
||||
|
||||
const template = validTemplates.find((t) => t.name === response.value)
|
||||
if (!template) throw new Error('Template is undefined')
|
||||
const template = validTemplates.find((t) => t.name === response)
|
||||
|
||||
return template
|
||||
}
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
import * as p from '@clack/prompts'
|
||||
import slugify from '@sindresorhus/slugify'
|
||||
import prompts from 'prompts'
|
||||
|
||||
import type { CliArgs, DbDetails, DbType } from '../types.js'
|
||||
|
||||
@@ -16,14 +16,14 @@ const dbChoiceRecord: Record<DbType, DbChoice> = {
|
||||
value: 'mongodb',
|
||||
},
|
||||
postgres: {
|
||||
dbConnectionPrefix: 'postgres://127.0.0.1:5432/',
|
||||
dbConnectionPrefix: 'postgres://postgres:<password>@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
|
||||
let dbType: DbType | symbol | undefined = undefined
|
||||
if (args['--db']) {
|
||||
if (!Object.values(dbChoiceRecord).some((dbChoice) => dbChoice.value === args['--db'])) {
|
||||
throw new Error(
|
||||
@@ -34,31 +34,20 @@ export async function selectDb(args: CliArgs, projectName: string): Promise<DbDe
|
||||
}
|
||||
dbType = args['--db'] as DbType
|
||||
} else {
|
||||
const dbTypeRes = await prompts(
|
||||
{
|
||||
name: 'value',
|
||||
type: 'select',
|
||||
choices: Object.values(dbChoiceRecord).map((dbChoice) => {
|
||||
return {
|
||||
title: dbChoice.title,
|
||||
value: dbChoice.value,
|
||||
}
|
||||
}),
|
||||
message: 'Select a database',
|
||||
validate: (value: string) => !!value.length,
|
||||
},
|
||||
{
|
||||
onCancel: () => {
|
||||
process.exit(0)
|
||||
},
|
||||
},
|
||||
)
|
||||
dbType = dbTypeRes.value
|
||||
dbType = await p.select<{ label: string; value: DbType }[], DbType>({
|
||||
initialValue: 'mongodb',
|
||||
message: `Select a database`,
|
||||
options: [
|
||||
{ label: 'MongoDB', value: 'mongodb' },
|
||||
{ label: 'Postgres', value: 'postgres' },
|
||||
],
|
||||
})
|
||||
if (p.isCancel(dbType)) process.exit(0)
|
||||
}
|
||||
|
||||
const dbChoice = dbChoiceRecord[dbType]
|
||||
|
||||
let dbUri: string | undefined = undefined
|
||||
let dbUri: string | symbol | undefined = undefined
|
||||
const initialDbUri = `${dbChoice.dbConnectionPrefix}${
|
||||
projectName === '.' ? `payload-${getRandomDigitSuffix()}` : slugify(projectName)
|
||||
}`
|
||||
@@ -68,21 +57,11 @@ export async function selectDb(args: CliArgs, projectName: string): Promise<DbDe
|
||||
} else if (args['--db-connection-string']) {
|
||||
dbUri = args['--db-connection-string']
|
||||
} else {
|
||||
const dbUriRes = await prompts(
|
||||
{
|
||||
name: 'value',
|
||||
type: 'text',
|
||||
initial: initialDbUri,
|
||||
message: `Enter ${dbChoice.title.split(' ')[0]} connection string`, // strip beta from title
|
||||
validate: (value: string) => !!value.length,
|
||||
},
|
||||
{
|
||||
onCancel: () => {
|
||||
process.exit(0)
|
||||
},
|
||||
},
|
||||
)
|
||||
dbUri = dbUriRes.value
|
||||
dbUri = await p.text({
|
||||
initialValue: initialDbUri,
|
||||
message: `Enter ${dbChoice.title.split(' ')[0]} connection string`, // strip beta from title
|
||||
})
|
||||
if (p.isCancel(dbUri)) process.exit(0)
|
||||
}
|
||||
|
||||
return {
|
||||
|
||||
@@ -18,43 +18,46 @@ export function getValidTemplates(): ProjectTemplate[] {
|
||||
name: 'blank-3.0',
|
||||
type: 'starter',
|
||||
description: 'Blank 3.0 Template',
|
||||
url: 'https://github.com/payloadcms/payload/templates/blank-3.0',
|
||||
},
|
||||
{
|
||||
name: 'blank',
|
||||
type: 'starter',
|
||||
description: 'Blank Template',
|
||||
url: 'https://github.com/payloadcms/payload/templates/blank',
|
||||
},
|
||||
{
|
||||
name: 'website',
|
||||
type: 'starter',
|
||||
description: 'Website Template',
|
||||
url: 'https://github.com/payloadcms/payload/templates/website',
|
||||
},
|
||||
{
|
||||
name: 'ecommerce',
|
||||
type: 'starter',
|
||||
description: 'E-commerce Template',
|
||||
url: 'https://github.com/payloadcms/payload/templates/ecommerce',
|
||||
url: 'https://github.com/payloadcms/payload/templates/blank-3.0#beta',
|
||||
},
|
||||
|
||||
// Remove these until they have been updated for 3.0
|
||||
|
||||
// {
|
||||
// name: 'blank',
|
||||
// type: 'starter',
|
||||
// description: 'Blank Template',
|
||||
// url: 'https://github.com/payloadcms/payload/templates/blank',
|
||||
// },
|
||||
// {
|
||||
// name: 'website',
|
||||
// type: 'starter',
|
||||
// description: 'Website Template',
|
||||
// url: 'https://github.com/payloadcms/payload/templates/website',
|
||||
// },
|
||||
// {
|
||||
// name: 'ecommerce',
|
||||
// type: 'starter',
|
||||
// description: 'E-commerce Template',
|
||||
// url: 'https://github.com/payloadcms/payload/templates/ecommerce',
|
||||
// },
|
||||
{
|
||||
name: 'plugin',
|
||||
type: 'plugin',
|
||||
description: 'Template for creating a Payload plugin',
|
||||
url: 'https://github.com/payloadcms/payload-plugin-template',
|
||||
},
|
||||
{
|
||||
name: 'payload-demo',
|
||||
type: 'starter',
|
||||
description: 'Payload demo site at https://demo.payloadcms.com',
|
||||
url: 'https://github.com/payloadcms/public-demo',
|
||||
},
|
||||
{
|
||||
name: 'payload-website',
|
||||
type: 'starter',
|
||||
description: 'Payload website CMS at https://payloadcms.com',
|
||||
url: 'https://github.com/payloadcms/website-cms',
|
||||
url: 'https://github.com/payloadcms/payload-plugin-template#beta',
|
||||
},
|
||||
// {
|
||||
// name: 'payload-demo',
|
||||
// type: 'starter',
|
||||
// description: 'Payload demo site at https://demo.payloadcms.com',
|
||||
// url: 'https://github.com/payloadcms/public-demo',
|
||||
// },
|
||||
// {
|
||||
// name: 'payload-website',
|
||||
// type: 'starter',
|
||||
// description: 'Payload website CMS at https://payloadcms.com',
|
||||
// url: 'https://github.com/payloadcms/website-cms',
|
||||
// },
|
||||
]
|
||||
}
|
||||
|
||||
@@ -1,54 +1,159 @@
|
||||
import { parseAndInsertWithPayload, withPayloadImportStatement } from './wrap-next-config.js'
|
||||
import { parseAndModifyConfigContent, withPayloadStatement } from './wrap-next-config.js'
|
||||
import * as p from '@clack/prompts'
|
||||
|
||||
const defaultNextConfig = `/** @type {import('next').NextConfig} */
|
||||
const esmConfigs = {
|
||||
defaultNextConfig: `/** @type {import('next').NextConfig} */
|
||||
const nextConfig = {};
|
||||
|
||||
export default nextConfig;
|
||||
`
|
||||
|
||||
const nextConfigWithFunc = `const nextConfig = {
|
||||
// Your Next.js config here
|
||||
}
|
||||
|
||||
export default someFunc(nextConfig)
|
||||
`
|
||||
const nextConfigWithFuncMultiline = `const nextConfig = {
|
||||
// Your Next.js config here
|
||||
}
|
||||
|
||||
`,
|
||||
nextConfigWithFunc: `const nextConfig = {};
|
||||
export default someFunc(nextConfig);
|
||||
`,
|
||||
nextConfigWithFuncMultiline: `const nextConfig = {};;
|
||||
export default someFunc(
|
||||
nextConfig
|
||||
)
|
||||
`
|
||||
|
||||
const nextConfigExportNamedDefault = `const nextConfig = {
|
||||
// Your Next.js config here
|
||||
);
|
||||
`,
|
||||
nextConfigExportNamedDefault: `const nextConfig = {};
|
||||
const wrapped = someFunc(asdf);
|
||||
export { wrapped as default };
|
||||
`,
|
||||
nextConfigWithSpread: `const nextConfig = {
|
||||
...someConfig,
|
||||
};
|
||||
export default nextConfig;
|
||||
`,
|
||||
}
|
||||
|
||||
const cjsConfigs = {
|
||||
defaultNextConfig: `
|
||||
/** @type {import('next').NextConfig} */
|
||||
const nextConfig = {};
|
||||
module.exports = nextConfig;
|
||||
`,
|
||||
anonConfig: `module.exports = {};`,
|
||||
nextConfigWithFunc: `const nextConfig = {};
|
||||
module.exports = someFunc(nextConfig);
|
||||
`,
|
||||
nextConfigWithFuncMultiline: `const nextConfig = {};
|
||||
module.exports = someFunc(
|
||||
nextConfig
|
||||
);
|
||||
`,
|
||||
nextConfigExportNamedDefault: `const nextConfig = {};
|
||||
const wrapped = someFunc(asdf);
|
||||
module.exports = wrapped;
|
||||
`,
|
||||
nextConfigWithSpread: `const nextConfig = { ...someConfig };
|
||||
module.exports = nextConfig;
|
||||
`,
|
||||
}
|
||||
const wrapped = someFunc(asdf)
|
||||
export { wrapped as default }
|
||||
`
|
||||
|
||||
describe('parseAndInsertWithPayload', () => {
|
||||
it('should parse the default next config', () => {
|
||||
const { modifiedConfigContent } = parseAndInsertWithPayload(defaultNextConfig)
|
||||
expect(modifiedConfigContent).toContain(withPayloadImportStatement)
|
||||
expect(modifiedConfigContent).toContain('withPayload(nextConfig)')
|
||||
})
|
||||
it('should parse the config with a function', () => {
|
||||
const { modifiedConfigContent } = parseAndInsertWithPayload(nextConfigWithFunc)
|
||||
expect(modifiedConfigContent).toContain('withPayload(someFunc(nextConfig))')
|
||||
describe('esm', () => {
|
||||
const configType = 'esm'
|
||||
const importStatement = withPayloadStatement[configType]
|
||||
it('should parse the default next config', () => {
|
||||
const { modifiedConfigContent } = parseAndModifyConfigContent(
|
||||
esmConfigs.defaultNextConfig,
|
||||
configType,
|
||||
)
|
||||
expect(modifiedConfigContent).toContain(importStatement)
|
||||
expect(modifiedConfigContent).toContain('withPayload(nextConfig)')
|
||||
})
|
||||
it('should parse the config with a function', () => {
|
||||
const { modifiedConfigContent } = parseAndModifyConfigContent(
|
||||
esmConfigs.nextConfigWithFunc,
|
||||
configType,
|
||||
)
|
||||
expect(modifiedConfigContent).toContain('withPayload(someFunc(nextConfig))')
|
||||
})
|
||||
|
||||
it('should parse the config with a function on a new line', () => {
|
||||
const { modifiedConfigContent } = parseAndModifyConfigContent(
|
||||
esmConfigs.nextConfigWithFuncMultiline,
|
||||
configType,
|
||||
)
|
||||
expect(modifiedConfigContent).toContain(importStatement)
|
||||
expect(modifiedConfigContent).toMatch(/withPayload\(someFunc\(\n nextConfig\n\)\)/)
|
||||
})
|
||||
|
||||
it('should parse the config with a spread', () => {
|
||||
const { modifiedConfigContent } = parseAndModifyConfigContent(
|
||||
esmConfigs.nextConfigWithSpread,
|
||||
configType,
|
||||
)
|
||||
expect(modifiedConfigContent).toContain(importStatement)
|
||||
expect(modifiedConfigContent).toContain('withPayload(nextConfig)')
|
||||
})
|
||||
|
||||
// Unsupported: export { wrapped as default }
|
||||
it('should give warning with a named export as default', () => {
|
||||
const warnLogSpy = jest.spyOn(p.log, 'warn').mockImplementation(() => {})
|
||||
|
||||
const { modifiedConfigContent, success } = parseAndModifyConfigContent(
|
||||
esmConfigs.nextConfigExportNamedDefault,
|
||||
configType,
|
||||
)
|
||||
expect(modifiedConfigContent).toContain(importStatement)
|
||||
expect(success).toBe(false)
|
||||
|
||||
expect(warnLogSpy).toHaveBeenCalledWith(
|
||||
expect.stringContaining('Could not automatically wrap'),
|
||||
)
|
||||
})
|
||||
})
|
||||
|
||||
it('should parse the config with a function on a new line', () => {
|
||||
const { modifiedConfigContent } = parseAndInsertWithPayload(nextConfigWithFuncMultiline)
|
||||
expect(modifiedConfigContent).toContain(withPayloadImportStatement)
|
||||
expect(modifiedConfigContent).toMatch(/withPayload\(someFunc\(\n nextConfig\n\)\)/)
|
||||
})
|
||||
describe('cjs', () => {
|
||||
const configType = 'cjs'
|
||||
const requireStatement = withPayloadStatement[configType]
|
||||
it('should parse the default next config', () => {
|
||||
const { modifiedConfigContent } = parseAndModifyConfigContent(
|
||||
cjsConfigs.defaultNextConfig,
|
||||
configType,
|
||||
)
|
||||
expect(modifiedConfigContent).toContain(requireStatement)
|
||||
expect(modifiedConfigContent).toContain('withPayload(nextConfig)')
|
||||
})
|
||||
it('should parse anonymous default config', () => {
|
||||
const { modifiedConfigContent } = parseAndModifyConfigContent(
|
||||
cjsConfigs.anonConfig,
|
||||
configType,
|
||||
)
|
||||
expect(modifiedConfigContent).toContain(requireStatement)
|
||||
expect(modifiedConfigContent).toContain('withPayload({})')
|
||||
})
|
||||
it('should parse the config with a function', () => {
|
||||
const { modifiedConfigContent } = parseAndModifyConfigContent(
|
||||
cjsConfigs.nextConfigWithFunc,
|
||||
configType,
|
||||
)
|
||||
expect(modifiedConfigContent).toContain('withPayload(someFunc(nextConfig))')
|
||||
})
|
||||
it('should parse the config with a function on a new line', () => {
|
||||
const { modifiedConfigContent } = parseAndModifyConfigContent(
|
||||
cjsConfigs.nextConfigWithFuncMultiline,
|
||||
configType,
|
||||
)
|
||||
expect(modifiedConfigContent).toContain(requireStatement)
|
||||
expect(modifiedConfigContent).toMatch(/withPayload\(someFunc\(\n nextConfig\n\)\)/)
|
||||
})
|
||||
it('should parse the config with a named export as default', () => {
|
||||
const { modifiedConfigContent } = parseAndModifyConfigContent(
|
||||
cjsConfigs.nextConfigExportNamedDefault,
|
||||
configType,
|
||||
)
|
||||
expect(modifiedConfigContent).toContain(requireStatement)
|
||||
expect(modifiedConfigContent).toContain('withPayload(wrapped)')
|
||||
})
|
||||
|
||||
// Unsupported: export { wrapped as default }
|
||||
it('should give warning with a named export as default', () => {
|
||||
const { modifiedConfigContent, error } = parseAndInsertWithPayload(nextConfigExportNamedDefault)
|
||||
expect(modifiedConfigContent).toContain(withPayloadImportStatement)
|
||||
expect(error).toBeTruthy()
|
||||
it('should parse the config with a spread', () => {
|
||||
const { modifiedConfigContent } = parseAndModifyConfigContent(
|
||||
cjsConfigs.nextConfigWithSpread,
|
||||
configType,
|
||||
)
|
||||
expect(modifiedConfigContent).toContain(requireStatement)
|
||||
expect(modifiedConfigContent).toContain('withPayload(nextConfig)')
|
||||
})
|
||||
})
|
||||
})
|
||||
|
||||
@@ -1,69 +1,159 @@
|
||||
import { parseModule } from 'esprima'
|
||||
import type { Program } from 'esprima-next'
|
||||
|
||||
import chalk from 'chalk'
|
||||
import { Syntax, parseModule } from 'esprima-next'
|
||||
import fs from 'fs'
|
||||
import globby from 'globby'
|
||||
import path from 'path'
|
||||
|
||||
export const withPayloadImportStatement = `import { withPayload } from '@payloadcms/next'\n`
|
||||
import { warning } from '../utils/log.js'
|
||||
import { log } from '../utils/log.js'
|
||||
|
||||
export const wrapNextConfig = async (args: { projectDir: string }): Promise<void> => {
|
||||
const foundConfig = (await globby('next.config.*js', { cwd: args.projectDir }))?.[0]
|
||||
|
||||
if (!foundConfig) {
|
||||
throw new Error(`No Next config found at ${args.projectDir}`)
|
||||
}
|
||||
const configPath = path.resolve(args.projectDir, foundConfig)
|
||||
const configContent = fs.readFileSync(configPath, 'utf8')
|
||||
const { error, modifiedConfigContent: newConfig } = parseAndInsertWithPayload(configContent)
|
||||
if (error) {
|
||||
console.warn(error)
|
||||
}
|
||||
fs.writeFileSync(configPath, newConfig)
|
||||
export const withPayloadStatement = {
|
||||
cjs: `const { withPayload } = require('@payloadcms/next/withPayload')\n`,
|
||||
esm: `import { withPayload } from '@payloadcms/next/withPayload'\n`,
|
||||
}
|
||||
|
||||
export function parseAndInsertWithPayload(content: string): {
|
||||
error?: string
|
||||
modifiedConfigContent: string
|
||||
} {
|
||||
content = withPayloadImportStatement + content
|
||||
const ast = parseModule(content, { loc: true })
|
||||
const exportDefaultDeclaration = ast.body.find((p) => p.type === 'ExportDefaultDeclaration') as
|
||||
| Directive
|
||||
| undefined
|
||||
type NextConfigType = 'cjs' | 'esm'
|
||||
|
||||
const exportNamedDeclaration = ast.body.find((p) => p.type === 'ExportNamedDeclaration') as
|
||||
| ExportNamedDeclaration
|
||||
| undefined
|
||||
export const wrapNextConfig = (args: {
|
||||
nextConfigPath: string
|
||||
nextConfigType: NextConfigType
|
||||
}) => {
|
||||
const { nextConfigPath, nextConfigType: configType } = args
|
||||
const configContent = fs.readFileSync(nextConfigPath, 'utf8')
|
||||
const { modifiedConfigContent: newConfig, success } = parseAndModifyConfigContent(
|
||||
configContent,
|
||||
configType,
|
||||
)
|
||||
|
||||
if (!exportDefaultDeclaration && !exportNamedDeclaration) {
|
||||
throw new Error('Could not find ExportDefaultDeclaration in next.config.js')
|
||||
if (!success) {
|
||||
return
|
||||
}
|
||||
|
||||
if (exportDefaultDeclaration) {
|
||||
const modifiedConfigContent = insertBeforeAndAfter(
|
||||
content,
|
||||
exportDefaultDeclaration.declaration?.loc,
|
||||
)
|
||||
return { modifiedConfigContent }
|
||||
} else if (exportNamedDeclaration) {
|
||||
const exportSpecifier = exportNamedDeclaration.specifiers.find(
|
||||
(s) =>
|
||||
s.type === 'ExportSpecifier' &&
|
||||
s.exported?.name === 'default' &&
|
||||
s.local?.type === 'Identifier' &&
|
||||
s.local?.name,
|
||||
)
|
||||
fs.writeFileSync(nextConfigPath, newConfig)
|
||||
}
|
||||
|
||||
if (exportSpecifier) {
|
||||
// TODO: Improve with this example and/or link to docs
|
||||
return {
|
||||
error: `Automatic wrapping of named exports as default not supported yet.
|
||||
Please manually wrap your Next config with the withPayload function`,
|
||||
modifiedConfigContent: content,
|
||||
/**
|
||||
* Parses config content with AST and wraps it with withPayload function
|
||||
*/
|
||||
export function parseAndModifyConfigContent(
|
||||
content: string,
|
||||
configType: NextConfigType,
|
||||
): { modifiedConfigContent: string; success: boolean } {
|
||||
content = withPayloadStatement[configType] + content
|
||||
|
||||
let ast: Program | undefined
|
||||
try {
|
||||
ast = parseModule(content, { loc: true })
|
||||
} catch (error: unknown) {
|
||||
if (error instanceof Error) {
|
||||
warning(`Unable to parse Next config. Error: ${error.message} `)
|
||||
warnUserWrapNotSuccessful(configType)
|
||||
}
|
||||
return {
|
||||
modifiedConfigContent: content,
|
||||
success: false,
|
||||
}
|
||||
}
|
||||
|
||||
if (configType === 'esm') {
|
||||
const exportDefaultDeclaration = ast.body.find(
|
||||
(p) => p.type === Syntax.ExportDefaultDeclaration,
|
||||
) as Directive | undefined
|
||||
|
||||
const exportNamedDeclaration = ast.body.find(
|
||||
(p) => p.type === Syntax.ExportNamedDeclaration,
|
||||
) as ExportNamedDeclaration | undefined
|
||||
|
||||
if (!exportDefaultDeclaration && !exportNamedDeclaration) {
|
||||
throw new Error('Could not find ExportDefaultDeclaration in next.config.js')
|
||||
}
|
||||
|
||||
if (exportDefaultDeclaration && exportDefaultDeclaration.declaration?.loc) {
|
||||
const modifiedConfigContent = insertBeforeAndAfter(
|
||||
content,
|
||||
exportDefaultDeclaration.declaration.loc,
|
||||
)
|
||||
return { modifiedConfigContent, success: true }
|
||||
} else if (exportNamedDeclaration) {
|
||||
const exportSpecifier = exportNamedDeclaration.specifiers.find(
|
||||
(s) =>
|
||||
s.type === 'ExportSpecifier' &&
|
||||
s.exported?.name === 'default' &&
|
||||
s.local?.type === 'Identifier' &&
|
||||
s.local?.name,
|
||||
)
|
||||
|
||||
if (exportSpecifier) {
|
||||
warning('Could not automatically wrap next.config.js with withPayload.')
|
||||
warning('Automatic wrapping of named exports as default not supported yet.')
|
||||
|
||||
warnUserWrapNotSuccessful(configType)
|
||||
return {
|
||||
modifiedConfigContent: content,
|
||||
success: false,
|
||||
}
|
||||
}
|
||||
}
|
||||
} else {
|
||||
throw new Error('Could not automatically wrap next.config.js with withPayload')
|
||||
|
||||
warning('Could not automatically wrap Next config with withPayload.')
|
||||
warnUserWrapNotSuccessful(configType)
|
||||
return {
|
||||
modifiedConfigContent: content,
|
||||
success: false,
|
||||
}
|
||||
} else if (configType === 'cjs') {
|
||||
// Find `module.exports = X`
|
||||
const moduleExports = ast.body.find(
|
||||
(p) =>
|
||||
p.type === Syntax.ExpressionStatement &&
|
||||
p.expression?.type === Syntax.AssignmentExpression &&
|
||||
p.expression.left?.type === Syntax.MemberExpression &&
|
||||
p.expression.left.object?.type === Syntax.Identifier &&
|
||||
p.expression.left.object.name === 'module' &&
|
||||
p.expression.left.property?.type === Syntax.Identifier &&
|
||||
p.expression.left.property.name === 'exports',
|
||||
// eslint-disable-next-line @typescript-eslint/no-explicit-any
|
||||
) as any
|
||||
|
||||
if (moduleExports && moduleExports.expression.right?.loc) {
|
||||
const modifiedConfigContent = insertBeforeAndAfter(
|
||||
content,
|
||||
moduleExports.expression.right.loc,
|
||||
)
|
||||
return { modifiedConfigContent, success: true }
|
||||
}
|
||||
|
||||
return {
|
||||
modifiedConfigContent: content,
|
||||
success: false,
|
||||
}
|
||||
}
|
||||
|
||||
warning('Could not automatically wrap Next config with withPayload.')
|
||||
warnUserWrapNotSuccessful(configType)
|
||||
return {
|
||||
modifiedConfigContent: content,
|
||||
success: false,
|
||||
}
|
||||
}
|
||||
|
||||
function warnUserWrapNotSuccessful(configType: NextConfigType) {
|
||||
// Output directions for user to update next.config.js
|
||||
const withPayloadMessage = `
|
||||
|
||||
${chalk.bold(`Please manually wrap your existing next.config.js with the withPayload function. Here is an example:`)}
|
||||
|
||||
${withPayloadStatement[configType]}
|
||||
|
||||
const nextConfig = {
|
||||
// Your Next.js config here
|
||||
}
|
||||
|
||||
${configType === 'esm' ? 'export default withPayload(nextConfig)' : 'module.exports = withPayload(nextConfig)'}
|
||||
|
||||
`
|
||||
|
||||
log(withPayloadMessage)
|
||||
}
|
||||
|
||||
type Directive = {
|
||||
|
||||
@@ -1,10 +1,9 @@
|
||||
import chalk from 'chalk'
|
||||
import fs from 'fs-extra'
|
||||
import path from 'path'
|
||||
|
||||
import type { CliArgs, ProjectTemplate } from '../types.js'
|
||||
|
||||
import { error, success } from '../utils/log.js'
|
||||
import { debug, error } from '../utils/log.js'
|
||||
|
||||
/** Parse and swap .env.example values and write .env */
|
||||
export async function writeEnvFile(args: {
|
||||
@@ -12,47 +11,54 @@ export async function writeEnvFile(args: {
|
||||
databaseUri: string
|
||||
payloadSecret: string
|
||||
projectDir: string
|
||||
template: ProjectTemplate
|
||||
template?: ProjectTemplate
|
||||
}): Promise<void> {
|
||||
const { cliArgs, databaseUri, payloadSecret, projectDir, template } = args
|
||||
|
||||
if (cliArgs['--dry-run']) {
|
||||
success(`DRY RUN: .env file created`)
|
||||
debug(`DRY RUN: .env file created`)
|
||||
return
|
||||
}
|
||||
|
||||
const envOutputPath = path.join(projectDir, '.env')
|
||||
|
||||
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
|
||||
if (fs.existsSync(envOutputPath)) {
|
||||
if (template?.type === 'starter') {
|
||||
// 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]
|
||||
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
|
||||
}
|
||||
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}`
|
||||
})
|
||||
return `${key}=${value}`
|
||||
})
|
||||
|
||||
// Write new .env file
|
||||
await fs.writeFile(path.join(projectDir, '.env'), envWithValues.join('\n'))
|
||||
// Write new .env file
|
||||
await fs.writeFile(envOutputPath, envWithValues.join('\n'))
|
||||
} else {
|
||||
const existingEnv = await fs.readFile(envOutputPath, 'utf8')
|
||||
const newEnv =
|
||||
existingEnv + `\nDATABASE_URI=${databaseUri}\nPAYLOAD_SECRET=${payloadSecret}\n`
|
||||
await fs.writeFile(envOutputPath, newEnv)
|
||||
}
|
||||
} else {
|
||||
const content = `MONGODB_URI=${databaseUri}\nPAYLOAD_SECRET=${payloadSecret}`
|
||||
const content = `DATABASE_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) {
|
||||
|
||||
@@ -1,21 +1,31 @@
|
||||
/* eslint-disable no-console */
|
||||
import * as p from '@clack/prompts'
|
||||
import slugify from '@sindresorhus/slugify'
|
||||
import arg from 'arg'
|
||||
import chalk from 'chalk'
|
||||
// @ts-expect-error no types
|
||||
import { detect } from 'detect-package-manager'
|
||||
import figures from 'figures'
|
||||
import path from 'path'
|
||||
|
||||
import type { CliArgs, PackageManager } from './types.js'
|
||||
|
||||
import { configurePayloadConfig } from './lib/configure-payload-config.js'
|
||||
import { createProject } from './lib/create-project.js'
|
||||
import { generateSecret } from './lib/generate-secret.js'
|
||||
import { initNext } from './lib/init-next.js'
|
||||
import { getNextAppDetails, initNext } from './lib/init-next.js'
|
||||
import { parseProjectName } from './lib/parse-project-name.js'
|
||||
import { parseTemplate } from './lib/parse-template.js'
|
||||
import { selectDb } from './lib/select-db.js'
|
||||
import { getValidTemplates, validateTemplate } from './lib/templates.js'
|
||||
import { writeEnvFile } from './lib/write-env-file.js'
|
||||
import { error, success } from './utils/log.js'
|
||||
import { helpMessage, successMessage, welcomeMessage } from './utils/messages.js'
|
||||
import { error, info } from './utils/log.js'
|
||||
import {
|
||||
feedbackOutro,
|
||||
helpMessage,
|
||||
moveMessage,
|
||||
successMessage,
|
||||
successfulNextInit,
|
||||
} from './utils/messages.js'
|
||||
|
||||
export class Main {
|
||||
args: CliArgs
|
||||
@@ -35,7 +45,7 @@ export class Main {
|
||||
'--template-branch': String,
|
||||
|
||||
// Next.js
|
||||
'--init-next': Boolean,
|
||||
'--init-next': Boolean, // TODO: Is this needed if we detect if inside Next.js project?
|
||||
|
||||
// Package manager
|
||||
'--no-deps': Boolean,
|
||||
@@ -61,42 +71,102 @@ export class Main {
|
||||
async init(): Promise<void> {
|
||||
try {
|
||||
if (this.args['--help']) {
|
||||
console.log(helpMessage())
|
||||
helpMessage()
|
||||
process.exit(0)
|
||||
}
|
||||
|
||||
const projectName = await parseProjectName(this.args)
|
||||
const projectDir = path.resolve(
|
||||
projectName === '.' || this.args['--init-next']
|
||||
? path.basename(process.cwd())
|
||||
: `./${slugify(projectName)}`,
|
||||
)
|
||||
// eslint-disable-next-line no-console
|
||||
console.log('\n')
|
||||
p.intro(chalk.bgCyan(chalk.black(' create-payload-app ')))
|
||||
p.note("Welcome to Payload. Let's create a project!")
|
||||
|
||||
// Detect if inside Next.js project
|
||||
const nextAppDetails = await getNextAppDetails(process.cwd())
|
||||
const { hasTopLevelLayout, nextAppDir, nextConfigPath } = nextAppDetails
|
||||
|
||||
if (nextConfigPath) {
|
||||
this.args['--name'] = slugify(path.basename(path.dirname(nextConfigPath)))
|
||||
}
|
||||
|
||||
const projectName = await parseProjectName(this.args)
|
||||
const projectDir = nextConfigPath
|
||||
? path.dirname(nextConfigPath)
|
||||
: path.resolve(process.cwd(), slugify(projectName))
|
||||
|
||||
console.log(welcomeMessage)
|
||||
const packageManager = await getPackageManager(this.args, projectDir)
|
||||
|
||||
if (this.args['--init-next']) {
|
||||
const result = await initNext({ ...this.args, packageManager })
|
||||
if (!result.success) {
|
||||
error(result.reason || 'Failed to initialize Payload app in Next.js project')
|
||||
} else {
|
||||
success('Payload app successfully initialized in Next.js project')
|
||||
if (nextConfigPath) {
|
||||
p.log.step(
|
||||
chalk.bold(`${chalk.bgBlack(` ${figures.triangleUp} Next.js `)} project detected!`),
|
||||
)
|
||||
|
||||
const proceed = await p.confirm({
|
||||
initialValue: true,
|
||||
message: chalk.bold(`Install ${chalk.green('Payload')} in this project?`),
|
||||
})
|
||||
if (p.isCancel(proceed) || !proceed) {
|
||||
p.outro(feedbackOutro())
|
||||
process.exit(0)
|
||||
}
|
||||
process.exit(result.success ? 0 : 1)
|
||||
// TODO: This should continue the normal prompt flow
|
||||
|
||||
// Check for top-level layout.tsx
|
||||
if (nextAppDir && hasTopLevelLayout) {
|
||||
p.log.warn(moveMessage({ nextAppDir, projectDir }))
|
||||
p.outro(feedbackOutro())
|
||||
process.exit(0)
|
||||
}
|
||||
|
||||
const dbDetails = await selectDb(this.args, projectName)
|
||||
|
||||
const result = await initNext({
|
||||
...this.args,
|
||||
dbType: dbDetails.type,
|
||||
nextAppDetails,
|
||||
packageManager,
|
||||
projectDir,
|
||||
})
|
||||
|
||||
if (result.success === false) {
|
||||
p.outro(feedbackOutro())
|
||||
process.exit(1)
|
||||
}
|
||||
|
||||
await configurePayloadConfig({
|
||||
dbDetails,
|
||||
projectDirOrConfigPath: {
|
||||
payloadConfigPath: result.payloadConfigPath,
|
||||
},
|
||||
})
|
||||
|
||||
await writeEnvFile({
|
||||
cliArgs: this.args,
|
||||
databaseUri: dbDetails.dbUri,
|
||||
payloadSecret: generateSecret(),
|
||||
projectDir,
|
||||
})
|
||||
|
||||
info('Payload project successfully initialized!')
|
||||
p.note(successfulNextInit(), chalk.bgGreen(chalk.black(' Documentation ')))
|
||||
p.outro(feedbackOutro())
|
||||
return
|
||||
}
|
||||
|
||||
const templateArg = this.args['--template']
|
||||
if (templateArg) {
|
||||
const valid = validateTemplate(templateArg)
|
||||
if (!valid) {
|
||||
console.log(helpMessage())
|
||||
helpMessage()
|
||||
process.exit(1)
|
||||
}
|
||||
}
|
||||
|
||||
const validTemplates = getValidTemplates()
|
||||
const template = await parseTemplate(this.args, validTemplates)
|
||||
if (!template) {
|
||||
p.log.error('Invalid template given')
|
||||
p.outro(feedbackOutro())
|
||||
process.exit(1)
|
||||
}
|
||||
|
||||
switch (template.type) {
|
||||
case 'starter': {
|
||||
@@ -131,10 +201,11 @@ export class Main {
|
||||
}
|
||||
}
|
||||
|
||||
success('Payload project successfully created')
|
||||
console.log(successMessage(projectDir, packageManager))
|
||||
} catch (error: unknown) {
|
||||
console.log(error)
|
||||
info('Payload project successfully created!')
|
||||
p.note(successMessage(projectDir, packageManager), chalk.bgGreen(chalk.black(' Next Steps ')))
|
||||
p.outro(feedbackOutro())
|
||||
} catch (err: unknown) {
|
||||
error(err instanceof Error ? err.message : 'An error occurred')
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -0,0 +1,35 @@
|
||||
import fs from 'fs'
|
||||
import fsp from 'fs/promises'
|
||||
import { fileURLToPath } from 'node:url'
|
||||
import path from 'path'
|
||||
const filename = fileURLToPath(import.meta.url)
|
||||
const dirname = path.dirname(filename)
|
||||
|
||||
// eslint-disable-next-line @typescript-eslint/no-floating-promises
|
||||
main()
|
||||
|
||||
/**
|
||||
* Copy the necessary template files from `templates/blank-3.0` to `dist/template`
|
||||
*
|
||||
* Eventually, this should be replaced with using tar.x to stream from the git repo
|
||||
*/
|
||||
|
||||
async function main() {
|
||||
const root = path.resolve(dirname, '../../../../')
|
||||
const outputPath = path.resolve(dirname, '../../dist/template')
|
||||
const sourceTemplatePath = path.resolve(root, 'templates/blank-3.0')
|
||||
|
||||
if (!fs.existsSync(sourceTemplatePath)) {
|
||||
throw new Error(`Source path does not exist: ${sourceTemplatePath}`)
|
||||
}
|
||||
|
||||
if (!fs.existsSync(outputPath)) {
|
||||
fs.mkdirSync(outputPath, { recursive: true })
|
||||
}
|
||||
|
||||
// Copy the src directory from `templates/blank-3.0` to `dist`
|
||||
const srcPath = path.resolve(sourceTemplatePath, 'src')
|
||||
const distSrcPath = path.resolve(outputPath, 'src')
|
||||
// Copy entire file structure from src to dist
|
||||
await fsp.cp(srcPath, distSrcPath, { recursive: true })
|
||||
}
|
||||
@@ -7,7 +7,7 @@ import path from 'path'
|
||||
export function copyRecursiveSync(src: string, dest: string, debug?: boolean) {
|
||||
const exists = fs.existsSync(src)
|
||||
const stats = exists && fs.statSync(src)
|
||||
const isDirectory = exists && stats.isDirectory()
|
||||
const isDirectory = exists && stats !== false && stats.isDirectory()
|
||||
if (isDirectory) {
|
||||
fs.mkdirSync(dest, { recursive: true })
|
||||
fs.readdirSync(src).forEach((childItemName) => {
|
||||
|
||||
@@ -1,27 +1,23 @@
|
||||
/* eslint-disable no-console */
|
||||
import * as p from '@clack/prompts'
|
||||
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))
|
||||
p.log.warn(chalk.yellow('? ') + chalk.bold(message))
|
||||
}
|
||||
|
||||
export const info = (message: string, paddingTop?: number): void => {
|
||||
console.log(
|
||||
`${'\n'.repeat(paddingTop || 0)}${chalk.green(figures.pointerSmall)} ${chalk.bold(message)}`,
|
||||
)
|
||||
export const info = (message: string): void => {
|
||||
p.log.step(chalk.bold(message))
|
||||
}
|
||||
|
||||
export const error = (message: string): void => {
|
||||
console.log(`${chalk.red(figures.cross)} ${chalk.bold(message)}`)
|
||||
p.log.error(chalk.bold(message))
|
||||
}
|
||||
|
||||
export const debug = (message: string): void => {
|
||||
console.log(
|
||||
`${chalk.gray(figures.pointerSmall)} ${chalk.bgGray('[DEBUG]')} ${chalk.gray(message)}`,
|
||||
)
|
||||
p.log.step(`${chalk.bgGray('[DEBUG]')} ${chalk.gray(message)}`)
|
||||
}
|
||||
|
||||
export const log = (message: string): void => {
|
||||
p.log.message(message)
|
||||
}
|
||||
|
||||
@@ -1,13 +1,14 @@
|
||||
/* eslint-disable no-console */
|
||||
import chalk from 'chalk'
|
||||
import figures from 'figures'
|
||||
import path from 'path'
|
||||
import terminalLink from 'terminal-link'
|
||||
|
||||
import type { ProjectTemplate } from '../types.js'
|
||||
import type { PackageManager } from '../types.js'
|
||||
|
||||
import { getValidTemplates } from '../lib/templates.js'
|
||||
|
||||
const header = (message: string): string => `${chalk.yellow(figures.star)} ${chalk.bold(message)}`
|
||||
const header = (message: string): string => chalk.bold(message)
|
||||
|
||||
export const welcomeMessage = chalk`
|
||||
{green Welcome to Payload. Let's create a project! }
|
||||
@@ -15,14 +16,20 @@ export const welcomeMessage = chalk`
|
||||
|
||||
const spacer = ' '.repeat(8)
|
||||
|
||||
export function helpMessage(): string {
|
||||
export function helpMessage(): void {
|
||||
const validTemplates = getValidTemplates()
|
||||
return chalk`
|
||||
console.log(chalk`
|
||||
{bold USAGE}
|
||||
|
||||
{dim Inside of an existing Next.js project}
|
||||
|
||||
{dim $} {bold npx create-payload-app}
|
||||
|
||||
{dim Create a new project from scratch}
|
||||
|
||||
{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
|
||||
{dim $} {bold npx create-payload-app} -n my-project -t template-name
|
||||
|
||||
{bold OPTIONS}
|
||||
|
||||
@@ -36,7 +43,7 @@ export function helpMessage(): string {
|
||||
--use-pnpm Use pnpm to install dependencies
|
||||
--no-deps Do not install any dependencies
|
||||
-h Show help
|
||||
`
|
||||
`)
|
||||
}
|
||||
|
||||
function formatTemplates(templates: ProjectTemplate[]) {
|
||||
@@ -45,29 +52,61 @@ function formatTemplates(templates: ProjectTemplate[]) {
|
||||
.join(`\n${spacer}`)}`
|
||||
}
|
||||
|
||||
export function successMessage(projectDir: string, packageManager: string): string {
|
||||
export function successMessage(projectDir: string, packageManager: PackageManager): string {
|
||||
const relativePath = path.relative(process.cwd(), projectDir)
|
||||
return `
|
||||
${header('Launch Application:')}
|
||||
${header('Launch Application:')}
|
||||
|
||||
- cd ${projectDir}
|
||||
- ${
|
||||
packageManager === 'yarn' ? 'yarn' : 'npm run'
|
||||
} dev or follow directions in ${createTerminalLink(
|
||||
'README.md',
|
||||
`file://${path.resolve(projectDir, 'README.md')}`,
|
||||
)}
|
||||
- cd ./${relativePath}
|
||||
- ${
|
||||
packageManager === 'npm' ? 'npm run' : packageManager
|
||||
} dev or follow directions in ${createTerminalLink(
|
||||
'README.md',
|
||||
`file://${path.resolve(projectDir, 'README.md')}`,
|
||||
)}
|
||||
|
||||
${header('Documentation:')}
|
||||
${header('Documentation:')}
|
||||
|
||||
- ${createTerminalLink(
|
||||
'Getting Started',
|
||||
'https://payloadcms.com/docs/getting-started/what-is-payload',
|
||||
)}
|
||||
- ${createTerminalLink('Configuration', 'https://payloadcms.com/docs/configuration/overview')}
|
||||
- ${createTerminalLink(
|
||||
'Getting Started',
|
||||
'https://payloadcms.com/docs/getting-started/what-is-payload',
|
||||
)}
|
||||
- ${createTerminalLink('Configuration', 'https://payloadcms.com/docs/configuration/overview')}
|
||||
|
||||
`
|
||||
}
|
||||
|
||||
export function successfulNextInit(): string {
|
||||
return `- ${createTerminalLink(
|
||||
'Getting Started',
|
||||
'https://payloadcms.com/docs/getting-started/what-is-payload',
|
||||
)}
|
||||
- ${createTerminalLink('Configuration', 'https://payloadcms.com/docs/configuration/overview')}
|
||||
`
|
||||
}
|
||||
|
||||
export function moveMessage(args: { nextAppDir: string; projectDir: string }): string {
|
||||
const relativeAppDir = path.relative(process.cwd(), args.nextAppDir)
|
||||
return `
|
||||
${header('Next Steps:')}
|
||||
|
||||
Payload does not support a top-level layout.tsx file in the app directory.
|
||||
|
||||
${chalk.bold('To continue:')}
|
||||
|
||||
- Create a new directory in ./${relativeAppDir} such as ./${relativeAppDir}/${chalk.bold('(app)')}
|
||||
- Move all files from ./${relativeAppDir} into that directory
|
||||
|
||||
It is recommended to do this from your IDE if your app has existing file references.
|
||||
|
||||
Once moved, rerun the create-payload-app command again.
|
||||
`
|
||||
}
|
||||
|
||||
export function feedbackOutro(): string {
|
||||
return `${chalk.bgCyan(chalk.black(' Have feedback? '))} Visit us on ${createTerminalLink('GitHub', 'https://github.com/payloadcms/payload')}.`
|
||||
}
|
||||
|
||||
// Create terminalLink with fallback for unsupported terminals
|
||||
function createTerminalLink(text: string, url: string) {
|
||||
return terminalLink(text, url, {
|
||||
|
||||
@@ -5,7 +5,8 @@
|
||||
"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. */
|
||||
"rootDir": "./src" /* Specify the root folder within your source files. */,
|
||||
"strict": true,
|
||||
},
|
||||
"exclude": ["dist", "build", "tests", "test", "node_modules", ".eslintrc.js"],
|
||||
"include": ["src/**/*.ts", "src/**/*.spec.ts", "src/**/*.tsx", "src/**/*.d.ts", "src/**/*.json"]
|
||||
|
||||
@@ -1,8 +1,12 @@
|
||||
{
|
||||
"name": "@payloadcms/db-mongodb",
|
||||
"version": "3.0.0-alpha.49",
|
||||
"description": "The officially supported MongoDB database adapter for Payload - Update 2",
|
||||
"repository": "https://github.com/payloadcms/payload",
|
||||
"version": "3.0.0-beta.15",
|
||||
"description": "The officially supported MongoDB database adapter for Payload",
|
||||
"repository": {
|
||||
"type": "git",
|
||||
"url": "https://github.com/payloadcms/payload.git",
|
||||
"directory": "packages/db-mongodb"
|
||||
},
|
||||
"license": "MIT",
|
||||
"homepage": "https://payloadcms.com",
|
||||
"type": "module",
|
||||
@@ -15,7 +19,7 @@
|
||||
"types": "./src/types.ts",
|
||||
"scripts": {
|
||||
"build": "pnpm build:swc && pnpm build:types",
|
||||
"build:swc": "swc ./src -d ./dist --config-file .swcrc",
|
||||
"build:swc": "swc ./src -d ./dist --config-file .swcrc-build",
|
||||
"build:types": "tsc --emitDeclarationOnly --outDir dist",
|
||||
"clean": "rimraf {dist,*.tsbuildinfo}",
|
||||
"prepublishOnly": "pnpm clean && pnpm turbo build"
|
||||
@@ -26,7 +30,6 @@
|
||||
"get-port": "5.1.1",
|
||||
"http-status": "1.6.2",
|
||||
"mongoose": "6.12.3",
|
||||
"mongoose-aggregate-paginate-v2": "1.0.6",
|
||||
"mongoose-paginate-v2": "1.7.22",
|
||||
"prompts": "2.4.2",
|
||||
"uuid": "9.0.0"
|
||||
|
||||
49
packages/db-mongodb/src/count.ts
Normal file
49
packages/db-mongodb/src/count.ts
Normal file
@@ -0,0 +1,49 @@
|
||||
import type { QueryOptions } from 'mongoose'
|
||||
import type { Count } from 'payload/database'
|
||||
import type { PayloadRequest } from 'payload/types'
|
||||
|
||||
import { flattenWhereToOperators } from 'payload/database'
|
||||
|
||||
import type { MongooseAdapter } from './index.js'
|
||||
|
||||
import { withSession } from './withSession.js'
|
||||
|
||||
export const count: Count = async function count(
|
||||
this: MongooseAdapter,
|
||||
{ collection, locale, req = {} as PayloadRequest, where },
|
||||
) {
|
||||
const Model = this.collections[collection]
|
||||
const options: QueryOptions = withSession(this, req.transactionID)
|
||||
|
||||
let hasNearConstraint = false
|
||||
|
||||
if (where) {
|
||||
const constraints = flattenWhereToOperators(where)
|
||||
hasNearConstraint = constraints.some((prop) => Object.keys(prop).some((key) => key === 'near'))
|
||||
}
|
||||
|
||||
const query = await Model.buildQuery({
|
||||
locale,
|
||||
payload: this.payload,
|
||||
where,
|
||||
})
|
||||
|
||||
// useEstimatedCount is faster, but not accurate, as it ignores any filters. It is thus set to true if there are no filters.
|
||||
const useEstimatedCount = hasNearConstraint || !query || Object.keys(query).length === 0
|
||||
|
||||
if (!useEstimatedCount && Object.keys(query).length === 0 && this.disableIndexHints !== true) {
|
||||
// Improve the performance of the countDocuments query which is used if useEstimatedCount is set to false by adding
|
||||
// a hint. By default, if no hint is provided, MongoDB does not use an indexed field to count the returned documents,
|
||||
// which makes queries very slow. This only happens when no query (filter) is provided. If one is provided, it uses
|
||||
// the correct indexed field
|
||||
options.hint = {
|
||||
_id: 1,
|
||||
}
|
||||
}
|
||||
|
||||
const result = await Model.countDocuments(query, options)
|
||||
|
||||
return {
|
||||
totalDocs: result,
|
||||
}
|
||||
}
|
||||
@@ -45,7 +45,9 @@ export const createMigration: CreateMigration = async function createMigration({
|
||||
|
||||
// Check if predefined migration exists
|
||||
if (fs.existsSync(cleanPath)) {
|
||||
let migration = await eval(`${require ? 'require' : 'import'}(${cleanPath})`)
|
||||
let migration = await eval(
|
||||
`${typeof require === 'function' ? 'require' : 'import'}(${cleanPath})`,
|
||||
)
|
||||
if ('default' in migration) migration = migration.default
|
||||
const { down, up } = migration
|
||||
|
||||
|
||||
@@ -12,6 +12,7 @@ import { createDatabaseAdapter } from 'payload/database'
|
||||
import type { CollectionModel, GlobalModel } from './types.js'
|
||||
|
||||
import { connect } from './connect.js'
|
||||
import { count } from './count.js'
|
||||
import { create } from './create.js'
|
||||
import { createGlobal } from './createGlobal.js'
|
||||
import { createGlobalVersion } from './createGlobalVersion.js'
|
||||
@@ -112,6 +113,7 @@ export function mongooseAdapter({
|
||||
collections: {},
|
||||
connectOptions: connectOptions || {},
|
||||
connection: undefined,
|
||||
count,
|
||||
disableIndexHints,
|
||||
globals: undefined,
|
||||
mongoMemoryServer,
|
||||
@@ -119,7 +121,6 @@ export function mongooseAdapter({
|
||||
transactionOptions: transactionOptions === false ? undefined : transactionOptions,
|
||||
url,
|
||||
versions: {},
|
||||
|
||||
// DatabaseAdapter
|
||||
beginTransaction: transactionOptions ? beginTransaction : undefined,
|
||||
commitTransaction,
|
||||
|
||||
@@ -5,11 +5,7 @@ import type { SanitizedCollectionConfig } from 'payload/types'
|
||||
|
||||
import mongoose from 'mongoose'
|
||||
import paginate from 'mongoose-paginate-v2'
|
||||
import {
|
||||
buildVersionCollectionFields,
|
||||
buildVersionGlobalFields,
|
||||
getVersionsModelName,
|
||||
} from 'payload/versions'
|
||||
import { buildVersionCollectionFields, buildVersionGlobalFields } from 'payload/versions'
|
||||
|
||||
import type { MongooseAdapter } from './index.js'
|
||||
import type { CollectionModel } from './types.js'
|
||||
@@ -18,19 +14,21 @@ import buildCollectionSchema from './models/buildCollectionSchema.js'
|
||||
import { buildGlobalModel } from './models/buildGlobalModel.js'
|
||||
import buildSchema from './models/buildSchema.js'
|
||||
import getBuildQueryPlugin from './queries/buildQuery.js'
|
||||
import { getDBName } from './utilities/getDBName.js'
|
||||
|
||||
export const init: Init = function init(this: MongooseAdapter) {
|
||||
this.payload.config.collections.forEach((collection: SanitizedCollectionConfig) => {
|
||||
const schema = buildCollectionSchema(collection, this.payload.config)
|
||||
|
||||
if (collection.versions) {
|
||||
const versionModelName = getVersionsModelName(collection)
|
||||
const versionModelName = getDBName({ config: collection, versions: true })
|
||||
|
||||
const versionCollectionFields = buildVersionCollectionFields(collection)
|
||||
|
||||
const versionSchema = buildSchema(this.payload.config, versionCollectionFields, {
|
||||
disableUnique: true,
|
||||
draftsEnabled: true,
|
||||
indexSortableFields: this.payload.config.indexSortableFields,
|
||||
options: {
|
||||
minimize: false,
|
||||
timestamps: false,
|
||||
@@ -54,17 +52,11 @@ export const init: Init = function init(this: MongooseAdapter) {
|
||||
}
|
||||
|
||||
const model = mongoose.model(
|
||||
collection.slug,
|
||||
getDBName({ config: collection }),
|
||||
schema,
|
||||
this.autoPluralization === true ? undefined : collection.slug,
|
||||
) as CollectionModel
|
||||
this.collections[collection.slug] = model
|
||||
|
||||
// TS expect error only needed until we launch 2.0.0
|
||||
// eslint-disable-next-line @typescript-eslint/ban-ts-comment
|
||||
this.payload.collections[collection.slug] = {
|
||||
config: collection,
|
||||
}
|
||||
})
|
||||
|
||||
const model = buildGlobalModel(this.payload.config)
|
||||
@@ -72,7 +64,7 @@ export const init: Init = function init(this: MongooseAdapter) {
|
||||
|
||||
this.payload.config.globals.forEach((global) => {
|
||||
if (global.versions) {
|
||||
const versionModelName = getVersionsModelName(global)
|
||||
const versionModelName = getDBName({ config: global, versions: true })
|
||||
|
||||
const versionGlobalFields = buildVersionGlobalFields(global)
|
||||
|
||||
|
||||
@@ -363,7 +363,7 @@ const fieldToSchemaMap: Record<string, FieldSchemaGenerator> = {
|
||||
}
|
||||
if (field.localized && config.localization) {
|
||||
config.localization.locales.forEach((locale) => {
|
||||
schema.index({ [`${field.name}.${locale}`]: '2dsphere' }, indexOptions)
|
||||
schema.index({ [`${field.name}.${locale.code}`]: '2dsphere' }, indexOptions)
|
||||
})
|
||||
} else {
|
||||
schema.index({ [field.name]: '2dsphere' }, indexOptions)
|
||||
|
||||
@@ -59,17 +59,12 @@ export async function buildSearchParam({
|
||||
let hasCustomID = false
|
||||
|
||||
if (sanitizedPath === '_id') {
|
||||
const customIDfield = payload.collections[collectionSlug]?.config.fields.find(
|
||||
(field) => fieldAffectsData(field) && field.name === 'id',
|
||||
)
|
||||
const customIDFieldType = payload.collections[collectionSlug]?.customIDType
|
||||
|
||||
let idFieldType: 'number' | 'text' = 'text'
|
||||
|
||||
if (customIDfield) {
|
||||
if (customIDfield?.type === 'text' || customIDfield?.type === 'number') {
|
||||
idFieldType = customIDfield.type
|
||||
}
|
||||
|
||||
if (customIDFieldType) {
|
||||
idFieldType = customIDFieldType
|
||||
hasCustomID = true
|
||||
}
|
||||
|
||||
@@ -213,18 +208,11 @@ export async function buildSearchParam({
|
||||
} else {
|
||||
;(Array.isArray(field.relationTo) ? field.relationTo : [field.relationTo]).forEach(
|
||||
(relationTo) => {
|
||||
const isRelatedToCustomNumberID = payload.collections[
|
||||
relationTo
|
||||
]?.config?.fields.find((relatedField) => {
|
||||
return (
|
||||
fieldAffectsData(relatedField) &&
|
||||
relatedField.name === 'id' &&
|
||||
relatedField.type === 'number'
|
||||
)
|
||||
})
|
||||
const isRelatedToCustomNumberID =
|
||||
payload.collections[relationTo]?.customIDType === 'number'
|
||||
|
||||
if (isRelatedToCustomNumberID) {
|
||||
if (isRelatedToCustomNumberID.type === 'number') hasNumberIDRelation = true
|
||||
hasNumberIDRelation = true
|
||||
}
|
||||
},
|
||||
)
|
||||
|
||||
@@ -1,18 +1,20 @@
|
||||
import { sanitizeConfig } from 'payload/config'
|
||||
import { SanitizedConfig, sanitizeConfig } from 'payload/config'
|
||||
import { Config } from 'payload/config'
|
||||
import { getLocalizedSortProperty } from './getLocalizedSortProperty.js'
|
||||
|
||||
const config = {
|
||||
const config = sanitizeConfig({
|
||||
localization: {
|
||||
locales: ['en', 'es'],
|
||||
defaultLocale: 'en',
|
||||
fallback: true,
|
||||
},
|
||||
} as Config
|
||||
} as Config) as SanitizedConfig
|
||||
|
||||
describe('get localized sort property', () => {
|
||||
it('passes through a non-localized sort property', () => {
|
||||
const result = getLocalizedSortProperty({
|
||||
segments: ['title'],
|
||||
config: sanitizeConfig(config),
|
||||
config,
|
||||
fields: [
|
||||
{
|
||||
name: 'title',
|
||||
@@ -28,7 +30,7 @@ describe('get localized sort property', () => {
|
||||
it('properly localizes an un-localized sort property', () => {
|
||||
const result = getLocalizedSortProperty({
|
||||
segments: ['title'],
|
||||
config: sanitizeConfig(config),
|
||||
config,
|
||||
fields: [
|
||||
{
|
||||
name: 'title',
|
||||
@@ -45,7 +47,7 @@ describe('get localized sort property', () => {
|
||||
it('keeps specifically asked-for localized sort properties', () => {
|
||||
const result = getLocalizedSortProperty({
|
||||
segments: ['title', 'es'],
|
||||
config: sanitizeConfig(config),
|
||||
config,
|
||||
fields: [
|
||||
{
|
||||
name: 'title',
|
||||
@@ -62,7 +64,7 @@ describe('get localized sort property', () => {
|
||||
it('properly localizes nested sort properties', () => {
|
||||
const result = getLocalizedSortProperty({
|
||||
segments: ['group', 'title'],
|
||||
config: sanitizeConfig(config),
|
||||
config,
|
||||
fields: [
|
||||
{
|
||||
name: 'group',
|
||||
@@ -85,7 +87,7 @@ describe('get localized sort property', () => {
|
||||
it('keeps requested locale with nested sort properties', () => {
|
||||
const result = getLocalizedSortProperty({
|
||||
segments: ['group', 'title', 'es'],
|
||||
config: sanitizeConfig(config),
|
||||
config,
|
||||
fields: [
|
||||
{
|
||||
name: 'group',
|
||||
@@ -108,7 +110,7 @@ describe('get localized sort property', () => {
|
||||
it('properly localizes field within row', () => {
|
||||
const result = getLocalizedSortProperty({
|
||||
segments: ['title'],
|
||||
config: sanitizeConfig(config),
|
||||
config,
|
||||
fields: [
|
||||
{
|
||||
type: 'row',
|
||||
@@ -130,7 +132,7 @@ describe('get localized sort property', () => {
|
||||
it('properly localizes field within named tab', () => {
|
||||
const result = getLocalizedSortProperty({
|
||||
segments: ['tab', 'title'],
|
||||
config: sanitizeConfig(config),
|
||||
config,
|
||||
fields: [
|
||||
{
|
||||
type: 'tabs',
|
||||
@@ -157,7 +159,7 @@ describe('get localized sort property', () => {
|
||||
it('properly localizes field within unnamed tab', () => {
|
||||
const result = getLocalizedSortProperty({
|
||||
segments: ['title'],
|
||||
config: sanitizeConfig(config),
|
||||
config,
|
||||
fields: [
|
||||
{
|
||||
type: 'tabs',
|
||||
|
||||
@@ -62,7 +62,7 @@ export const sanitizeQueryValue = ({
|
||||
formattedValue = Number(val)
|
||||
}
|
||||
|
||||
if (field.type === 'date' && typeof val === 'string') {
|
||||
if (field.type === 'date' && typeof val === 'string' && operator !== 'exists') {
|
||||
formattedValue = new Date(val)
|
||||
if (Number.isNaN(Date.parse(formattedValue))) {
|
||||
return undefined
|
||||
@@ -142,7 +142,10 @@ export const sanitizeQueryValue = ({
|
||||
|
||||
if (path !== '_id' || (path === '_id' && hasCustomID && field.type === 'text')) {
|
||||
if (operator === 'contains') {
|
||||
formattedValue = { $options: 'i', $regex: formattedValue }
|
||||
formattedValue = {
|
||||
$options: 'i',
|
||||
$regex: formattedValue.replace(/[\\^$*+?.()|[\]{}]/g, '\\$&'),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -6,6 +6,10 @@ export const commitTransaction: CommitTransaction = async function commitTransac
|
||||
}
|
||||
|
||||
await this.sessions[id].commitTransaction()
|
||||
await this.sessions[id].endSession()
|
||||
try {
|
||||
await this.sessions[id].endSession()
|
||||
} catch (error) {
|
||||
// ending sessions is only best effort and won't impact anything if it fails since the transaction was committed
|
||||
}
|
||||
delete this.sessions[id]
|
||||
}
|
||||
|
||||
41
packages/db-mongodb/src/utilities/getDBName.ts
Normal file
41
packages/db-mongodb/src/utilities/getDBName.ts
Normal file
@@ -0,0 +1,41 @@
|
||||
import type { DBIdentifierName } from 'payload/database'
|
||||
|
||||
type Args = {
|
||||
config: {
|
||||
dbName?: DBIdentifierName
|
||||
enumName?: DBIdentifierName
|
||||
name?: string
|
||||
slug?: string
|
||||
}
|
||||
locales?: boolean
|
||||
target?: 'dbName' | 'enumName'
|
||||
versions?: boolean
|
||||
}
|
||||
|
||||
/**
|
||||
* Used to name database enums and collections
|
||||
* Returns the collection or enum name for a given entity
|
||||
*/
|
||||
export const getDBName = ({
|
||||
config: { name, slug },
|
||||
config,
|
||||
target = 'dbName',
|
||||
versions = false,
|
||||
}: Args): string => {
|
||||
let result: string
|
||||
let custom = config[target]
|
||||
|
||||
if (!custom && target === 'enumName') {
|
||||
custom = config['dbName']
|
||||
}
|
||||
|
||||
if (custom) {
|
||||
result = typeof custom === 'function' ? custom({}) : custom
|
||||
} else {
|
||||
result = name ?? slug
|
||||
}
|
||||
|
||||
if (versions) result = `_${result}_versions`
|
||||
|
||||
return result
|
||||
}
|
||||
@@ -1,8 +1,12 @@
|
||||
{
|
||||
"name": "@payloadcms/db-postgres",
|
||||
"version": "3.0.0-alpha.49",
|
||||
"version": "3.0.0-beta.15",
|
||||
"description": "The officially supported Postgres database adapter for Payload",
|
||||
"repository": "https://github.com/payloadcms/payload",
|
||||
"repository": {
|
||||
"type": "git",
|
||||
"url": "https://github.com/payloadcms/payload.git",
|
||||
"directory": "packages/db-postgres"
|
||||
},
|
||||
"license": "MIT",
|
||||
"homepage": "https://payloadcms.com",
|
||||
"type": "module",
|
||||
|
||||
65
packages/db-postgres/src/count.ts
Normal file
65
packages/db-postgres/src/count.ts
Normal file
@@ -0,0 +1,65 @@
|
||||
import type { Count } from 'payload/database'
|
||||
import type { SanitizedCollectionConfig } from 'payload/types'
|
||||
|
||||
import { sql } from 'drizzle-orm'
|
||||
|
||||
import type { ChainedMethods } from './find/chainMethods.js'
|
||||
import type { PostgresAdapter } from './types.js'
|
||||
|
||||
import { chainMethods } from './find/chainMethods.js'
|
||||
import buildQuery from './queries/buildQuery.js'
|
||||
import { getTableName } from './schema/getTableName.js'
|
||||
|
||||
export const count: Count = async function count(
|
||||
this: PostgresAdapter,
|
||||
{ collection, locale, req, where: whereArg },
|
||||
) {
|
||||
const collectionConfig: SanitizedCollectionConfig = this.payload.collections[collection].config
|
||||
|
||||
const tableName = getTableName({
|
||||
adapter: this,
|
||||
config: collectionConfig,
|
||||
})
|
||||
|
||||
const db = this.sessions[req.transactionID]?.db || this.drizzle
|
||||
const table = this.tables[tableName]
|
||||
|
||||
const { joinAliases, joins, where } = await buildQuery({
|
||||
adapter: this,
|
||||
fields: collectionConfig.fields,
|
||||
locale,
|
||||
tableName,
|
||||
where: whereArg,
|
||||
})
|
||||
|
||||
const selectCountMethods: ChainedMethods = []
|
||||
|
||||
joinAliases.forEach(({ condition, table }) => {
|
||||
selectCountMethods.push({
|
||||
args: [table, condition],
|
||||
method: 'leftJoin',
|
||||
})
|
||||
})
|
||||
|
||||
Object.entries(joins).forEach(([joinTable, condition]) => {
|
||||
if (joinTable) {
|
||||
selectCountMethods.push({
|
||||
args: [this.tables[joinTable], condition],
|
||||
method: 'leftJoin',
|
||||
})
|
||||
}
|
||||
})
|
||||
|
||||
const countResult = await chainMethods({
|
||||
methods: selectCountMethods,
|
||||
query: db
|
||||
.select({
|
||||
count: sql<number>`count
|
||||
(DISTINCT ${this.tables[tableName].id})`,
|
||||
})
|
||||
.from(table)
|
||||
.where(where),
|
||||
})
|
||||
|
||||
return { totalDocs: Number(countResult[0].count) }
|
||||
}
|
||||
@@ -1,9 +1,8 @@
|
||||
import type { Create } from 'payload/database'
|
||||
|
||||
import toSnakeCase from 'to-snake-case'
|
||||
|
||||
import type { PostgresAdapter } from './types.js'
|
||||
|
||||
import { getTableName } from './schema/getTableName.js'
|
||||
import { upsertRow } from './upsertRow/index.js'
|
||||
|
||||
export const create: Create = async function create(
|
||||
@@ -20,7 +19,10 @@ export const create: Create = async function create(
|
||||
fields: collection.fields,
|
||||
operation: 'create',
|
||||
req,
|
||||
tableName: toSnakeCase(collectionSlug),
|
||||
tableName: getTableName({
|
||||
adapter: this,
|
||||
config: collection,
|
||||
}),
|
||||
})
|
||||
|
||||
return result
|
||||
|
||||
@@ -1,10 +1,9 @@
|
||||
import type { CreateGlobalArgs } from 'payload/database'
|
||||
import type { PayloadRequest, TypeWithID } from 'payload/types'
|
||||
|
||||
import toSnakeCase from 'to-snake-case'
|
||||
|
||||
import type { PostgresAdapter } from './types.js'
|
||||
|
||||
import { getTableName } from './schema/getTableName.js'
|
||||
import { upsertRow } from './upsertRow/index.js'
|
||||
|
||||
export async function createGlobal<T extends TypeWithID>(
|
||||
@@ -21,7 +20,10 @@ export async function createGlobal<T extends TypeWithID>(
|
||||
fields: globalConfig.fields,
|
||||
operation: 'create',
|
||||
req,
|
||||
tableName: toSnakeCase(slug),
|
||||
tableName: getTableName({
|
||||
adapter: this,
|
||||
config: globalConfig,
|
||||
}),
|
||||
})
|
||||
|
||||
return result
|
||||
|
||||
@@ -4,10 +4,10 @@ import type { PayloadRequest, TypeWithID } from 'payload/types'
|
||||
import { sql } from 'drizzle-orm'
|
||||
import { type CreateGlobalVersionArgs } from 'payload/database'
|
||||
import { buildVersionGlobalFields } from 'payload/versions'
|
||||
import toSnakeCase from 'to-snake-case'
|
||||
|
||||
import type { PostgresAdapter } from './types.js'
|
||||
|
||||
import { getTableName } from './schema/getTableName.js'
|
||||
import { upsertRow } from './upsertRow/index.js'
|
||||
|
||||
export async function createGlobalVersion<T extends TypeWithID>(
|
||||
@@ -16,8 +16,11 @@ export async function createGlobalVersion<T extends TypeWithID>(
|
||||
) {
|
||||
const db = this.sessions[req.transactionID]?.db || this.drizzle
|
||||
const global = this.payload.globals.config.find(({ slug }) => slug === globalSlug)
|
||||
const globalTableName = toSnakeCase(globalSlug)
|
||||
const tableName = `_${globalTableName}_v`
|
||||
const tableName = getTableName({
|
||||
adapter: this,
|
||||
config: global,
|
||||
versions: true,
|
||||
})
|
||||
|
||||
const result = await upsertRow<TypeWithVersion<T>>({
|
||||
adapter: this,
|
||||
|
||||
@@ -3,15 +3,17 @@ import type { DrizzleSnapshotJSON } from 'drizzle-kit/payload'
|
||||
import type { CreateMigration } from 'payload/database'
|
||||
|
||||
import fs from 'fs'
|
||||
import { createRequire } from 'module'
|
||||
import prompts from 'prompts'
|
||||
|
||||
import type { PostgresAdapter } from './types.js'
|
||||
|
||||
const require = createRequire(import.meta.url)
|
||||
|
||||
const migrationTemplate = (
|
||||
upSQL?: string,
|
||||
downSQL?: string,
|
||||
) => `import { MigrateUpArgs, MigrateDownArgs } from '@payloadcms/db-postgres'
|
||||
import { sql } from 'drizzle-orm'
|
||||
) => `import { MigrateUpArgs, MigrateDownArgs, sql } from '@payloadcms/db-postgres'
|
||||
|
||||
export async function up({ payload }: MigrateUpArgs): Promise<void> {
|
||||
${
|
||||
@@ -60,9 +62,7 @@ export const createMigration: CreateMigration = async function createMigration(
|
||||
fs.mkdirSync(dir)
|
||||
}
|
||||
|
||||
const { generateDrizzleJson, generateMigration } = require
|
||||
? require('drizzle-kit/payload')
|
||||
: await import('drizzle-kit/payload')
|
||||
const { generateDrizzleJson, generateMigration } = require('drizzle-kit/payload')
|
||||
|
||||
const [yyymmdd, hhmmss] = new Date().toISOString().split('T')
|
||||
const formattedDate = yyymmdd.replace(/\D/g, '')
|
||||
|
||||
@@ -3,10 +3,10 @@ import type { PayloadRequest, TypeWithID } from 'payload/types'
|
||||
|
||||
import { sql } from 'drizzle-orm'
|
||||
import { buildVersionCollectionFields } from 'payload/versions'
|
||||
import toSnakeCase from 'to-snake-case'
|
||||
|
||||
import type { PostgresAdapter } from './types.js'
|
||||
|
||||
import { getTableName } from './schema/getTableName.js'
|
||||
import { upsertRow } from './upsertRow/index.js'
|
||||
|
||||
export async function createVersion<T extends TypeWithID>(
|
||||
@@ -21,8 +21,11 @@ export async function createVersion<T extends TypeWithID>(
|
||||
) {
|
||||
const db = this.sessions[req.transactionID]?.db || this.drizzle
|
||||
const collection = this.payload.collections[collectionSlug].config
|
||||
const collectionTableName = toSnakeCase(collectionSlug)
|
||||
const tableName = `_${collectionTableName}_v`
|
||||
const tableName = getTableName({
|
||||
adapter: this,
|
||||
config: collection,
|
||||
versions: true,
|
||||
})
|
||||
|
||||
const result = await upsertRow<TypeWithVersion<T>>({
|
||||
adapter: this,
|
||||
@@ -40,7 +43,15 @@ export async function createVersion<T extends TypeWithID>(
|
||||
})
|
||||
|
||||
const table = this.tables[tableName]
|
||||
const relationshipsTable = this.tables[`${tableName}_rels`]
|
||||
const relationshipsTable =
|
||||
this.tables[
|
||||
getTableName({
|
||||
adapter: this,
|
||||
config: collection,
|
||||
relationships: true,
|
||||
versions: true,
|
||||
})
|
||||
]
|
||||
|
||||
if (collection.versions.drafts) {
|
||||
await db.execute(sql`
|
||||
|
||||
@@ -2,11 +2,11 @@ import type { DeleteMany } from 'payload/database'
|
||||
import type { PayloadRequest } from 'payload/types'
|
||||
|
||||
import { inArray } from 'drizzle-orm'
|
||||
import toSnakeCase from 'to-snake-case'
|
||||
|
||||
import type { PostgresAdapter } from './types.js'
|
||||
|
||||
import { findMany } from './find/findMany.js'
|
||||
import { getTableName } from './schema/getTableName.js'
|
||||
|
||||
export const deleteMany: DeleteMany = async function deleteMany(
|
||||
this: PostgresAdapter,
|
||||
@@ -14,7 +14,7 @@ export const deleteMany: DeleteMany = async function deleteMany(
|
||||
) {
|
||||
const db = this.sessions[req.transactionID]?.db || this.drizzle
|
||||
const collectionConfig = this.payload.collections[collection].config
|
||||
const tableName = toSnakeCase(collection)
|
||||
const tableName = getTableName({ adapter: this, config: collectionConfig })
|
||||
|
||||
const result = await findMany({
|
||||
adapter: this,
|
||||
|
||||
@@ -2,13 +2,13 @@ import type { DeleteOne } from 'payload/database'
|
||||
import type { PayloadRequest } from 'payload/types'
|
||||
|
||||
import { eq } from 'drizzle-orm'
|
||||
import toSnakeCase from 'to-snake-case'
|
||||
|
||||
import type { PostgresAdapter } from './types.js'
|
||||
|
||||
import { buildFindManyArgs } from './find/buildFindManyArgs.js'
|
||||
import buildQuery from './queries/buildQuery.js'
|
||||
import { selectDistinct } from './queries/selectDistinct.js'
|
||||
import { getTableName } from './schema/getTableName.js'
|
||||
import { transform } from './transform/read/index.js'
|
||||
|
||||
export const deleteOne: DeleteOne = async function deleteOne(
|
||||
@@ -17,7 +17,10 @@ export const deleteOne: DeleteOne = async function deleteOne(
|
||||
) {
|
||||
const db = this.sessions[req.transactionID]?.db || this.drizzle
|
||||
const collection = this.payload.collections[collectionSlug].config
|
||||
const tableName = toSnakeCase(collectionSlug)
|
||||
const tableName = getTableName({
|
||||
adapter: this,
|
||||
config: collection,
|
||||
})
|
||||
let docToDelete: Record<string, unknown>
|
||||
|
||||
const { joinAliases, joins, selectFields, where } = await buildQuery({
|
||||
|
||||
@@ -3,11 +3,11 @@ import type { PayloadRequest, SanitizedCollectionConfig } from 'payload/types'
|
||||
|
||||
import { inArray } from 'drizzle-orm'
|
||||
import { buildVersionCollectionFields } from 'payload/versions'
|
||||
import toSnakeCase from 'to-snake-case'
|
||||
|
||||
import type { PostgresAdapter } from './types.js'
|
||||
|
||||
import { findMany } from './find/findMany.js'
|
||||
import { getTableName } from './schema/getTableName.js'
|
||||
|
||||
export const deleteVersions: DeleteVersions = async function deleteVersion(
|
||||
this: PostgresAdapter,
|
||||
@@ -16,7 +16,11 @@ export const deleteVersions: DeleteVersions = async function deleteVersion(
|
||||
const db = this.sessions[req.transactionID]?.db || this.drizzle
|
||||
const collectionConfig: SanitizedCollectionConfig = this.payload.collections[collection].config
|
||||
|
||||
const tableName = `_${toSnakeCase(collection)}_v`
|
||||
const tableName = getTableName({
|
||||
adapter: this,
|
||||
config: collectionConfig,
|
||||
versions: true,
|
||||
})
|
||||
const fields = buildVersionCollectionFields(collectionConfig)
|
||||
|
||||
const { docs } = await findMany({
|
||||
|
||||
@@ -2,13 +2,13 @@ import type { Destroy } from 'payload/database'
|
||||
|
||||
import type { PostgresAdapter } from './types.js'
|
||||
|
||||
import { pushDevSchema } from './utilities/pushDevSchema.js'
|
||||
|
||||
// eslint-disable-next-line @typescript-eslint/require-await
|
||||
export const destroy: Destroy = async function destroy(this: PostgresAdapter) {
|
||||
if (process.env.NODE_ENV !== 'production') {
|
||||
await pushDevSchema(this)
|
||||
} else {
|
||||
// TODO: this hangs test suite for some reason
|
||||
// await this.pool.end()
|
||||
}
|
||||
this.enums = {}
|
||||
this.schema = {}
|
||||
this.tables = {}
|
||||
this.relations = {}
|
||||
this.blockTableNames = {}
|
||||
this.fieldConstraints = {}
|
||||
this.drizzle = undefined
|
||||
}
|
||||
|
||||
@@ -1,38 +1,41 @@
|
||||
import type { Find } from 'payload/database'
|
||||
import type { PayloadRequest, SanitizedCollectionConfig } from 'payload/types'
|
||||
|
||||
import toSnakeCase from 'to-snake-case'
|
||||
|
||||
import type { PostgresAdapter } from './types.js'
|
||||
|
||||
import { findMany } from './find/findMany.js'
|
||||
import { getTableName } from './schema/getTableName.js'
|
||||
|
||||
export const find: Find = async function find(
|
||||
this: PostgresAdapter,
|
||||
{
|
||||
collection,
|
||||
limit: limitArg,
|
||||
limit,
|
||||
locale,
|
||||
page = 1,
|
||||
pagination,
|
||||
req = {} as PayloadRequest,
|
||||
sort: sortArg,
|
||||
where: whereArg,
|
||||
where,
|
||||
},
|
||||
) {
|
||||
const collectionConfig: SanitizedCollectionConfig = this.payload.collections[collection].config
|
||||
const sort = typeof sortArg === 'string' ? sortArg : collectionConfig.defaultSort
|
||||
const tableName = getTableName({
|
||||
adapter: this,
|
||||
config: collectionConfig,
|
||||
})
|
||||
|
||||
return findMany({
|
||||
adapter: this,
|
||||
fields: collectionConfig.fields,
|
||||
limit: limitArg,
|
||||
limit,
|
||||
locale,
|
||||
page,
|
||||
pagination,
|
||||
req,
|
||||
sort,
|
||||
tableName: toSnakeCase(collection),
|
||||
where: whereArg,
|
||||
tableName,
|
||||
where,
|
||||
})
|
||||
}
|
||||
|
||||
@@ -120,7 +120,7 @@ export const findMany = async function find({
|
||||
|
||||
const findPromise = db.query[tableName].findMany(findManyArgs)
|
||||
|
||||
if (pagination !== false && (orderedIDs ? orderedIDs?.length >= limit : true)) {
|
||||
if (pagination !== false && (orderedIDs ? orderedIDs?.length <= limit : true)) {
|
||||
const selectCountMethods: ChainedMethods = []
|
||||
|
||||
joinAliases.forEach(({ condition, table }) => {
|
||||
|
||||
@@ -2,11 +2,12 @@
|
||||
import type { Field } from 'payload/types'
|
||||
|
||||
import { fieldAffectsData, tabHasName } from 'payload/types'
|
||||
import toSnakeCase from 'to-snake-case'
|
||||
|
||||
import type { PostgresAdapter } from '../types.js'
|
||||
import type { Result } from './buildFindManyArgs.js'
|
||||
|
||||
import { getTableName } from '../schema/getTableName.js'
|
||||
|
||||
type TraverseFieldArgs = {
|
||||
_locales: Record<string, unknown>
|
||||
adapter: PostgresAdapter
|
||||
@@ -78,9 +79,22 @@ export const traverseFields = ({
|
||||
with: {},
|
||||
}
|
||||
|
||||
const arrayTableName = `${currentTableName}_${path}${toSnakeCase(field.name)}`
|
||||
const arrayTableName = getTableName({
|
||||
adapter,
|
||||
config: field,
|
||||
parentTableName: currentTableName,
|
||||
prefix: `${currentTableName}_${path}`,
|
||||
})
|
||||
|
||||
if (adapter.tables[`${arrayTableName}_locales`]) withArray.with._locales = _locales
|
||||
const arrayTableNameWithLocales = getTableName({
|
||||
adapter,
|
||||
config: field,
|
||||
locales: true,
|
||||
parentTableName: currentTableName,
|
||||
prefix: `${currentTableName}_${path}`,
|
||||
})
|
||||
|
||||
if (adapter.tables[arrayTableNameWithLocales]) withArray.with._locales = _locales
|
||||
currentArgs.with[`${path}${field.name}`] = withArray
|
||||
|
||||
traverseFields({
|
||||
@@ -128,9 +142,16 @@ export const traverseFields = ({
|
||||
with: {},
|
||||
}
|
||||
|
||||
const tableName = `${topLevelTableName}_blocks_${toSnakeCase(block.slug)}`
|
||||
const tableName = getTableName({
|
||||
adapter,
|
||||
config: block,
|
||||
parentTableName: topLevelTableName,
|
||||
prefix: `${topLevelTableName}_blocks_`,
|
||||
})
|
||||
|
||||
if (adapter.tables[`${tableName}_locales`]) withBlock.with._locales = _locales
|
||||
if (adapter.tables[`${tableName}${adapter.localesSuffix}`]) {
|
||||
withBlock.with._locales = _locales
|
||||
}
|
||||
topLevelArgs.with[blockKey] = withBlock
|
||||
|
||||
traverseFields({
|
||||
|
||||
@@ -1,17 +1,19 @@
|
||||
import type { FindGlobal } from 'payload/database'
|
||||
|
||||
import toSnakeCase from 'to-snake-case'
|
||||
|
||||
import type { PostgresAdapter } from './types.js'
|
||||
|
||||
import { findMany } from './find/findMany.js'
|
||||
import { getTableName } from './schema/getTableName.js'
|
||||
|
||||
export const findGlobal: FindGlobal = async function findGlobal(
|
||||
this: PostgresAdapter,
|
||||
{ slug, locale, req, where },
|
||||
) {
|
||||
const globalConfig = this.payload.globals.config.find((config) => config.slug === slug)
|
||||
const tableName = toSnakeCase(slug)
|
||||
const tableName = getTableName({
|
||||
adapter: this,
|
||||
config: globalConfig,
|
||||
})
|
||||
|
||||
const {
|
||||
docs: [doc],
|
||||
|
||||
@@ -2,11 +2,11 @@ import type { FindGlobalVersions } from 'payload/database'
|
||||
import type { PayloadRequest, SanitizedGlobalConfig } from 'payload/types'
|
||||
|
||||
import { buildVersionGlobalFields } from 'payload/versions'
|
||||
import toSnakeCase from 'to-snake-case'
|
||||
|
||||
import type { PostgresAdapter } from './types.js'
|
||||
|
||||
import { findMany } from './find/findMany.js'
|
||||
import { getTableName } from './schema/getTableName.js'
|
||||
|
||||
export const findGlobalVersions: FindGlobalVersions = async function findGlobalVersions(
|
||||
this: PostgresAdapter,
|
||||
@@ -27,7 +27,11 @@ export const findGlobalVersions: FindGlobalVersions = async function findGlobalV
|
||||
)
|
||||
const sort = typeof sortArg === 'string' ? sortArg : '-createdAt'
|
||||
|
||||
const tableName = `_${toSnakeCase(global)}_v`
|
||||
const tableName = getTableName({
|
||||
adapter: this,
|
||||
config: globalConfig,
|
||||
versions: true,
|
||||
})
|
||||
const fields = buildVersionGlobalFields(globalConfig)
|
||||
|
||||
return findMany({
|
||||
|
||||
@@ -1,17 +1,20 @@
|
||||
import type { FindOneArgs } from 'payload/database'
|
||||
import type { PayloadRequest, SanitizedCollectionConfig, TypeWithID } from 'payload/types'
|
||||
|
||||
import toSnakeCase from 'to-snake-case'
|
||||
|
||||
import type { PostgresAdapter } from './types.js'
|
||||
|
||||
import { findMany } from './find/findMany.js'
|
||||
import { getTableName } from './schema/getTableName.js'
|
||||
|
||||
export async function findOne<T extends TypeWithID>(
|
||||
this: PostgresAdapter,
|
||||
{ collection, locale, req = {} as PayloadRequest, where: incomingWhere }: FindOneArgs,
|
||||
{ collection, locale, req = {} as PayloadRequest, where }: FindOneArgs,
|
||||
): Promise<T> {
|
||||
const collectionConfig: SanitizedCollectionConfig = this.payload.collections[collection].config
|
||||
const tableName = getTableName({
|
||||
adapter: this,
|
||||
config: collectionConfig,
|
||||
})
|
||||
|
||||
const { docs } = await findMany({
|
||||
adapter: this,
|
||||
@@ -22,8 +25,8 @@ export async function findOne<T extends TypeWithID>(
|
||||
pagination: false,
|
||||
req,
|
||||
sort: undefined,
|
||||
tableName: toSnakeCase(collection),
|
||||
where: incomingWhere,
|
||||
tableName,
|
||||
where,
|
||||
})
|
||||
|
||||
return docs?.[0] || null
|
||||
|
||||
@@ -2,11 +2,11 @@ import type { FindVersions } from 'payload/database'
|
||||
import type { PayloadRequest, SanitizedCollectionConfig } from 'payload/types'
|
||||
|
||||
import { buildVersionCollectionFields } from 'payload/versions'
|
||||
import toSnakeCase from 'to-snake-case'
|
||||
|
||||
import type { PostgresAdapter } from './types.js'
|
||||
|
||||
import { findMany } from './find/findMany.js'
|
||||
import { getTableName } from './schema/getTableName.js'
|
||||
|
||||
export const findVersions: FindVersions = async function findVersions(
|
||||
this: PostgresAdapter,
|
||||
@@ -25,7 +25,11 @@ export const findVersions: FindVersions = async function findVersions(
|
||||
const collectionConfig: SanitizedCollectionConfig = this.payload.collections[collection].config
|
||||
const sort = typeof sortArg === 'string' ? sortArg : collectionConfig.defaultSort
|
||||
|
||||
const tableName = `_${toSnakeCase(collection)}_v`
|
||||
const tableName = getTableName({
|
||||
adapter: this,
|
||||
config: collectionConfig,
|
||||
versions: true,
|
||||
})
|
||||
const fields = buildVersionCollectionFields(collectionConfig)
|
||||
|
||||
return findMany({
|
||||
|
||||
@@ -8,6 +8,7 @@ import { createDatabaseAdapter } from 'payload/database'
|
||||
import type { Args, PostgresAdapter } from './types.js'
|
||||
|
||||
import { connect } from './connect.js'
|
||||
import { count } from './count.js'
|
||||
import { create } from './create.js'
|
||||
import { createGlobal } from './createGlobal.js'
|
||||
import { createGlobalVersion } from './createGlobalVersion.js'
|
||||
@@ -40,43 +41,49 @@ import { updateVersion } from './updateVersion.js'
|
||||
|
||||
export type { MigrateDownArgs, MigrateUpArgs } from './types.js'
|
||||
|
||||
export { sql } from 'drizzle-orm'
|
||||
|
||||
export function postgresAdapter(args: Args): DatabaseAdapterObj<PostgresAdapter> {
|
||||
const postgresIDType = args.idType || 'serial'
|
||||
const payloadIDType = postgresIDType === 'serial' ? 'number' : 'text'
|
||||
|
||||
function adapter({ payload }: { payload: Payload }) {
|
||||
const migrationDir = findMigrationDir(args.migrationDir)
|
||||
const idType = args.idType || 'serial'
|
||||
|
||||
return createDatabaseAdapter<PostgresAdapter>({
|
||||
name: 'postgres',
|
||||
|
||||
// Postgres-specific
|
||||
blockTableNames: {},
|
||||
drizzle: undefined,
|
||||
enums: {},
|
||||
fieldConstraints: {},
|
||||
idType,
|
||||
idType: postgresIDType,
|
||||
localesSuffix: args.localesSuffix || '_locales',
|
||||
logger: args.logger,
|
||||
pgSchema: undefined,
|
||||
pool: undefined,
|
||||
poolOptions: args.pool,
|
||||
push: args.push,
|
||||
relations: {},
|
||||
relationshipsSuffix: args.relationshipsSuffix || '_rels',
|
||||
schema: {},
|
||||
schemaName: args.schemaName,
|
||||
sessions: {},
|
||||
tables: {},
|
||||
versionsSuffix: args.versionsSuffix || '_v',
|
||||
|
||||
// DatabaseAdapter
|
||||
beginTransaction,
|
||||
commitTransaction,
|
||||
connect,
|
||||
count,
|
||||
create,
|
||||
createGlobal,
|
||||
createGlobalVersion,
|
||||
createMigration,
|
||||
createVersion,
|
||||
/**
|
||||
* This represents how a default ID is treated in Payload as were a field type
|
||||
*/
|
||||
defaultIDType: idType === 'serial' ? 'number' : 'text',
|
||||
defaultIDType: payloadIDType,
|
||||
deleteMany,
|
||||
deleteOne,
|
||||
deleteVersions,
|
||||
@@ -105,7 +112,7 @@ export function postgresAdapter(args: Args): DatabaseAdapterObj<PostgresAdapter>
|
||||
}
|
||||
|
||||
return {
|
||||
defaultIDType: 'number',
|
||||
defaultIDType: payloadIDType,
|
||||
init: adapter,
|
||||
}
|
||||
}
|
||||
|
||||
@@ -4,11 +4,11 @@ import type { SanitizedCollectionConfig } from 'payload/types'
|
||||
|
||||
import { pgEnum, pgSchema, pgTable } from 'drizzle-orm/pg-core'
|
||||
import { buildVersionCollectionFields, buildVersionGlobalFields } from 'payload/versions'
|
||||
import toSnakeCase from 'to-snake-case'
|
||||
|
||||
import type { PostgresAdapter } from './types.js'
|
||||
|
||||
import { buildTable } from './schema/build.js'
|
||||
import { getTableName } from './schema/getTableName.js'
|
||||
|
||||
export const init: Init = function init(this: PostgresAdapter) {
|
||||
if (this.schemaName) {
|
||||
@@ -25,7 +25,10 @@ export const init: Init = function init(this: PostgresAdapter) {
|
||||
}
|
||||
|
||||
this.payload.config.collections.forEach((collection: SanitizedCollectionConfig) => {
|
||||
const tableName = toSnakeCase(collection.slug)
|
||||
const tableName = getTableName({
|
||||
adapter: this,
|
||||
config: collection,
|
||||
})
|
||||
|
||||
buildTable({
|
||||
adapter: this,
|
||||
@@ -37,10 +40,15 @@ export const init: Init = function init(this: PostgresAdapter) {
|
||||
fields: collection.fields,
|
||||
tableName,
|
||||
timestamps: collection.timestamps,
|
||||
versions: false,
|
||||
})
|
||||
|
||||
if (collection.versions) {
|
||||
const versionsTableName = `_${tableName}_v`
|
||||
const versionsTableName = getTableName({
|
||||
adapter: this,
|
||||
config: collection,
|
||||
versions: true,
|
||||
})
|
||||
const versionFields = buildVersionCollectionFields(collection)
|
||||
|
||||
buildTable({
|
||||
@@ -53,12 +61,13 @@ export const init: Init = function init(this: PostgresAdapter) {
|
||||
fields: versionFields,
|
||||
tableName: versionsTableName,
|
||||
timestamps: true,
|
||||
versions: true,
|
||||
})
|
||||
}
|
||||
})
|
||||
|
||||
this.payload.config.globals.forEach((global) => {
|
||||
const tableName = toSnakeCase(global.slug)
|
||||
const tableName = getTableName({ adapter: this, config: global })
|
||||
|
||||
buildTable({
|
||||
adapter: this,
|
||||
@@ -70,10 +79,11 @@ export const init: Init = function init(this: PostgresAdapter) {
|
||||
fields: global.fields,
|
||||
tableName,
|
||||
timestamps: false,
|
||||
versions: false,
|
||||
})
|
||||
|
||||
if (global.versions) {
|
||||
const versionsTableName = `_${tableName}_v`
|
||||
const versionsTableName = getTableName({ adapter: this, config: global, versions: true })
|
||||
const versionFields = buildVersionGlobalFields(global)
|
||||
|
||||
buildTable({
|
||||
@@ -86,6 +96,7 @@ export const init: Init = function init(this: PostgresAdapter) {
|
||||
fields: versionFields,
|
||||
tableName: versionsTableName,
|
||||
timestamps: true,
|
||||
versions: true,
|
||||
})
|
||||
}
|
||||
})
|
||||
|
||||
@@ -3,6 +3,7 @@ import type { Payload } from 'payload'
|
||||
import type { Migration } from 'payload/database'
|
||||
import type { PayloadRequest } from 'payload/types'
|
||||
|
||||
import { createRequire } from 'module'
|
||||
import {
|
||||
commitTransaction,
|
||||
initTransaction,
|
||||
@@ -17,6 +18,8 @@ import { createMigrationTable } from './utilities/createMigrationTable.js'
|
||||
import { migrationTableExists } from './utilities/migrationTableExists.js'
|
||||
import { parseError } from './utilities/parseError.js'
|
||||
|
||||
const require = createRequire(import.meta.url)
|
||||
|
||||
export async function migrate(this: PostgresAdapter): Promise<void> {
|
||||
const { payload } = this
|
||||
const migrationFiles = await readMigrationFiles({ payload })
|
||||
@@ -82,16 +85,14 @@ export async function migrate(this: PostgresAdapter): Promise<void> {
|
||||
}
|
||||
|
||||
async function runMigrationFile(payload: Payload, migration: Migration, batch: number) {
|
||||
const { generateDrizzleJson } = require
|
||||
? require('drizzle-kit/payload')
|
||||
: await import('drizzle-kit/payload')
|
||||
const { generateDrizzleJson } = require('drizzle-kit/payload')
|
||||
|
||||
const start = Date.now()
|
||||
const req = { payload } as PayloadRequest
|
||||
|
||||
payload.logger.info({ msg: `Migrating: ${migration.name}` })
|
||||
|
||||
const pgAdapter = payload.db as PostgresAdapter
|
||||
const pgAdapter = payload.db
|
||||
const drizzleJSON = generateDrizzleJson(pgAdapter.schema)
|
||||
|
||||
try {
|
||||
|
||||
@@ -14,6 +14,8 @@ import { v4 as uuid } from 'uuid'
|
||||
import type { GenericColumn, GenericTable, PostgresAdapter } from '../types.js'
|
||||
import type { BuildQueryJoinAliases, BuildQueryJoins } from './buildQuery.js'
|
||||
|
||||
import { getTableName } from '../schema/getTableName.js'
|
||||
|
||||
type Constraint = {
|
||||
columnName: string
|
||||
table: GenericTable | PgTableWithColumns<any>
|
||||
@@ -183,7 +185,13 @@ export const getTableColumnFromPath = ({
|
||||
|
||||
case 'group': {
|
||||
if (locale && field.localized && adapter.payload.config.localization) {
|
||||
newTableName = `${tableName}_locales`
|
||||
newTableName = getTableName({
|
||||
adapter,
|
||||
config: field,
|
||||
locales: true,
|
||||
parentTableName: tableName,
|
||||
prefix: `${tableName}_`,
|
||||
})
|
||||
|
||||
joins[tableName] = eq(
|
||||
adapter.tables[tableName].id,
|
||||
@@ -217,8 +225,92 @@ export const getTableColumnFromPath = ({
|
||||
})
|
||||
}
|
||||
|
||||
case 'select': {
|
||||
if (field.hasMany) {
|
||||
newTableName = getTableName({
|
||||
adapter,
|
||||
config: field,
|
||||
parentTableName: `${tableName}_${tableNameSuffix}`,
|
||||
prefix: `${tableName}_${tableNameSuffix}`,
|
||||
})
|
||||
|
||||
if (locale && field.localized && adapter.payload.config.localization) {
|
||||
joins[newTableName] = and(
|
||||
eq(adapter.tables[tableName].id, adapter.tables[newTableName].parent),
|
||||
eq(adapter.tables[newTableName]._locale, locale),
|
||||
)
|
||||
if (locale !== 'all') {
|
||||
constraints.push({
|
||||
columnName: '_locale',
|
||||
table: adapter.tables[newTableName],
|
||||
value: locale,
|
||||
})
|
||||
}
|
||||
} else {
|
||||
joins[newTableName] = eq(
|
||||
adapter.tables[tableName].id,
|
||||
adapter.tables[newTableName].parent,
|
||||
)
|
||||
}
|
||||
|
||||
return {
|
||||
columnName: 'value',
|
||||
constraints,
|
||||
field,
|
||||
table: adapter.tables[newTableName],
|
||||
}
|
||||
}
|
||||
break
|
||||
}
|
||||
|
||||
case 'text':
|
||||
case 'number': {
|
||||
if (field.hasMany) {
|
||||
let tableType = 'texts'
|
||||
let columnName = 'text'
|
||||
if (field.type === 'number') {
|
||||
tableType = 'numbers'
|
||||
columnName = 'number'
|
||||
}
|
||||
newTableName = `${tableName}_${tableType}`
|
||||
const joinConstraints = [
|
||||
eq(adapter.tables[tableName].id, adapter.tables[newTableName].parent),
|
||||
eq(adapter.tables[newTableName].path, `${constraintPath}${field.name}`),
|
||||
]
|
||||
|
||||
if (locale && field.localized && adapter.payload.config.localization) {
|
||||
joins[newTableName] = and(
|
||||
...joinConstraints,
|
||||
eq(adapter.tables[newTableName]._locale, locale),
|
||||
)
|
||||
if (locale !== 'all') {
|
||||
constraints.push({
|
||||
columnName: 'locale',
|
||||
table: adapter.tables[newTableName],
|
||||
value: locale,
|
||||
})
|
||||
}
|
||||
} else {
|
||||
joins[newTableName] = and(...joinConstraints)
|
||||
}
|
||||
|
||||
return {
|
||||
columnName,
|
||||
constraints,
|
||||
field,
|
||||
table: adapter.tables[newTableName],
|
||||
}
|
||||
}
|
||||
break
|
||||
}
|
||||
|
||||
case 'array': {
|
||||
newTableName = `${tableName}_${tableNameSuffix}${toSnakeCase(field.name)}`
|
||||
newTableName = getTableName({
|
||||
adapter,
|
||||
config: field,
|
||||
parentTableName: `${tableName}_${tableNameSuffix}`,
|
||||
prefix: `${tableName}_${tableNameSuffix}`,
|
||||
})
|
||||
constraintPath = `${constraintPath}${field.name}.%.`
|
||||
if (locale && field.localized && adapter.payload.config.localization) {
|
||||
joins[newTableName] = and(
|
||||
@@ -265,7 +357,12 @@ export const getTableColumnFromPath = ({
|
||||
const blockTypes = Array.isArray(value) ? value : [value]
|
||||
blockTypes.forEach((blockType) => {
|
||||
const block = field.blocks.find((block) => block.slug === blockType)
|
||||
newTableName = `${tableName}_blocks_${toSnakeCase(block.slug)}`
|
||||
newTableName = getTableName({
|
||||
adapter,
|
||||
config: block,
|
||||
parentTableName: tableName,
|
||||
prefix: `${tableName}_blocks_`,
|
||||
})
|
||||
joins[newTableName] = eq(
|
||||
adapter.tables[tableName].id,
|
||||
adapter.tables[newTableName]._parentID,
|
||||
@@ -285,7 +382,12 @@ export const getTableColumnFromPath = ({
|
||||
}
|
||||
|
||||
const hasBlockField = field.blocks.some((block) => {
|
||||
newTableName = `${tableName}_blocks_${toSnakeCase(block.slug)}`
|
||||
newTableName = getTableName({
|
||||
adapter,
|
||||
config: block,
|
||||
parentTableName: tableName,
|
||||
prefix: `${tableName}_blocks_`,
|
||||
})
|
||||
constraintPath = `${constraintPath}${field.name}.%.`
|
||||
let result
|
||||
const blockConstraints = []
|
||||
@@ -351,7 +453,7 @@ export const getTableColumnFromPath = ({
|
||||
case 'relationship':
|
||||
case 'upload': {
|
||||
let relationshipFields
|
||||
const relationTableName = `${rootTableName}_rels`
|
||||
const relationTableName = `${rootTableName}${adapter.relationshipsSuffix}`
|
||||
const newCollectionPath = pathSegments.slice(1).join('.')
|
||||
const aliasRelationshipTableName = uuid()
|
||||
const aliasRelationshipTable = alias(
|
||||
@@ -360,22 +462,45 @@ export const getTableColumnFromPath = ({
|
||||
)
|
||||
|
||||
// Join in the relationships table
|
||||
joinAliases.push({
|
||||
condition: and(
|
||||
eq((aliasTable || adapter.tables[rootTableName]).id, aliasRelationshipTable.parent),
|
||||
like(aliasRelationshipTable.path, `${constraintPath}${field.name}`),
|
||||
),
|
||||
table: aliasRelationshipTable,
|
||||
})
|
||||
if (locale && field.localized && adapter.payload.config.localization) {
|
||||
joinAliases.push({
|
||||
condition: and(
|
||||
eq((aliasTable || adapter.tables[rootTableName]).id, aliasRelationshipTable.parent),
|
||||
eq(aliasRelationshipTable.locale, locale),
|
||||
like(aliasRelationshipTable.path, `${constraintPath}${field.name}`),
|
||||
),
|
||||
table: aliasRelationshipTable,
|
||||
})
|
||||
if (locale !== 'all') {
|
||||
constraints.push({
|
||||
columnName: 'locale',
|
||||
table: aliasRelationshipTable,
|
||||
value: locale,
|
||||
})
|
||||
}
|
||||
} else {
|
||||
// Join in the relationships table
|
||||
joinAliases.push({
|
||||
condition: and(
|
||||
eq((aliasTable || adapter.tables[rootTableName]).id, aliasRelationshipTable.parent),
|
||||
like(aliasRelationshipTable.path, `${constraintPath}${field.name}`),
|
||||
),
|
||||
table: aliasRelationshipTable,
|
||||
})
|
||||
}
|
||||
|
||||
selectFields[`${relationTableName}.path`] = aliasRelationshipTable.path
|
||||
|
||||
let newAliasTable
|
||||
|
||||
if (typeof field.relationTo === 'string') {
|
||||
newTableName = `${toSnakeCase(field.relationTo)}`
|
||||
const relationshipConfig = adapter.payload.collections[field.relationTo].config
|
||||
newTableName = getTableName({
|
||||
adapter,
|
||||
config: relationshipConfig,
|
||||
})
|
||||
// parent to relationship join table
|
||||
relationshipFields = adapter.payload.collections[field.relationTo].config.fields
|
||||
relationshipFields = relationshipConfig.fields
|
||||
|
||||
newAliasTable = alias(adapter.tables[newTableName], toSnakeCase(uuid()))
|
||||
|
||||
@@ -394,7 +519,11 @@ export const getTableColumnFromPath = ({
|
||||
}
|
||||
} else if (newCollectionPath === 'value') {
|
||||
const tableColumnsNames = field.relationTo.map(
|
||||
(relationTo) => `"${aliasRelationshipTableName}"."${toSnakeCase(relationTo)}_id"`,
|
||||
(relationTo) =>
|
||||
`"${aliasRelationshipTableName}"."${getTableName({
|
||||
adapter,
|
||||
config: adapter.payload.collections[relationTo].config,
|
||||
})}_id"`,
|
||||
)
|
||||
return {
|
||||
constraints,
|
||||
@@ -435,43 +564,41 @@ export const getTableColumnFromPath = ({
|
||||
value,
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
default: {
|
||||
if (fieldAffectsData(field)) {
|
||||
if (field.localized && adapter.payload.config.localization) {
|
||||
// If localized, we go to localized table and set aliasTable to undefined
|
||||
// so it is not picked up below to be used as targetTable
|
||||
newTableName = `${tableName}_locales`
|
||||
if (fieldAffectsData(field)) {
|
||||
if (field.localized && adapter.payload.config.localization) {
|
||||
// If localized, we go to localized table and set aliasTable to undefined
|
||||
// so it is not picked up below to be used as targetTable
|
||||
newTableName = `${tableName}${adapter.localesSuffix}`
|
||||
|
||||
const parentTable = aliasTable || adapter.tables[tableName]
|
||||
const parentTable = aliasTable || adapter.tables[tableName]
|
||||
|
||||
joins[newTableName] = eq(parentTable.id, adapter.tables[newTableName]._parentID)
|
||||
joins[newTableName] = eq(parentTable.id, adapter.tables[newTableName]._parentID)
|
||||
|
||||
aliasTable = undefined
|
||||
aliasTable = undefined
|
||||
|
||||
if (locale !== 'all') {
|
||||
constraints.push({
|
||||
columnName: '_locale',
|
||||
table: adapter.tables[newTableName],
|
||||
value: locale,
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
const targetTable = aliasTable || adapter.tables[newTableName]
|
||||
|
||||
selectFields[`${newTableName}.${columnPrefix}${field.name}`] =
|
||||
targetTable[`${columnPrefix}${field.name}`]
|
||||
|
||||
return {
|
||||
columnName: `${columnPrefix}${field.name}`,
|
||||
constraints,
|
||||
field,
|
||||
pathSegments,
|
||||
table: targetTable,
|
||||
}
|
||||
if (locale !== 'all') {
|
||||
constraints.push({
|
||||
columnName: '_locale',
|
||||
table: adapter.tables[newTableName],
|
||||
value: locale,
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
const targetTable = aliasTable || adapter.tables[newTableName]
|
||||
|
||||
selectFields[`${newTableName}.${columnPrefix}${field.name}`] =
|
||||
targetTable[`${columnPrefix}${field.name}`]
|
||||
|
||||
return {
|
||||
columnName: `${columnPrefix}${field.name}`,
|
||||
constraints,
|
||||
field,
|
||||
pathSegments,
|
||||
table: targetTable,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -66,7 +66,7 @@ export const sanitizeQueryValue = ({
|
||||
formattedValue = Number(val)
|
||||
}
|
||||
|
||||
if (field.type === 'date') {
|
||||
if (field.type === 'date' && operator !== 'exists') {
|
||||
if (typeof val === 'string') {
|
||||
formattedValue = new Date(val)
|
||||
if (Number.isNaN(Date.parse(formattedValue))) {
|
||||
|
||||
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user