Compare commits

..

666 Commits

Author SHA1 Message Date
Dan Ribbens
3a0eb0e710 fix: api keys work after updating to null 2024-04-24 21:21:45 -04:00
Dan Ribbens
b156a8beb7 fix: disable api key checkbox does not remove api key 2024-04-24 21:13:50 -04:00
Dan Ribbens
d074f3f407 test: auth e2e disable api key falsely passing 2024-04-24 21:11:56 -04:00
Jacob Fletcher
6f6c1435c7 fix(ui): renders stay logged in modal (#6009) 2024-04-24 16:19:11 -04:00
Elliot DeNolf
332b8b6f34 ci(scripts): true publish with pLimit 2024-04-24 15:26:24 -04:00
Dan Ribbens
94f1dfef52 fix: bulk publish (#6007) 2024-04-24 15:05:02 -04:00
Elliot DeNolf
71f19fba58 chore(release): v3.0.0-beta.15 [skip ci] 2024-04-24 13:41:28 -04:00
Paul
24b18fb0fd feat!: removed getDataAndFile and getLocales from createPayloadRequest in favour of new utilities addDataAndFileToRequest and addLocalesToRequest (#5999) 2024-04-24 13:31:54 -03:00
Elliot DeNolf
5731241a5c fix(db-postgres): postgres uuid (#6003)
Co-authored-by: James <james@trbl.design>
2024-04-24 11:59:39 -04:00
Dan Ribbens
47e70abb4e fix: type collection config missing dbName (#5983) 2024-04-24 11:32:59 -04:00
Paul
0ede95f375 fix: issues creating the first user (#5986)
Co-authored-by: Jarrod Flesch <jarrodmflesch@gmail.com>
2024-04-24 11:30:52 -04:00
Jarrod Flesch
b723efdd3b chore: fixing flakey tests (#5984) 2024-04-24 00:44:43 -04:00
Elliot DeNolf
14c513690d ci: lint pr titles (#5988) 2024-04-23 23:40:55 -04:00
Alessio Gravili
88f239e784 feat(richtext-lexical)!: rework population behavior and allow richText adapter field hooks (#5893)
BREAKING:

- Unpopulated lexical relationship, link and upload nodes now save the relationTo document ID under value instead of value.id. This matches the behavior of core relationship fields. This changes the shape of the saved JSON data
- Any custom features which add their own population promises need to be reworked. populationPromises no longer accepts the promises as a return value. Instead, it expects you to mutate the promises array which is passed through, which mimics the way it works in core
2024-04-23 20:43:07 -04:00
Alessio Gravili
a1f6bf8a67 fix(richtext-lexical): Heading feature: enabledHeadingSizes not being applied 2024-04-23 20:37:11 -04:00
Alessio Gravili
912dcd38df fix(richtext-lexical): add missing HorizontalRuleFeature export 2024-04-23 20:25:12 -04:00
Alessio Gravili
da5028cdee feat(richtext-lexical): show loading indicator while block nodes are loading 2024-04-23 20:22:18 -04:00
Elliot DeNolf
899faa62f1 chore: update pnpm-lock 2024-04-23 17:01:57 -04:00
Alessio Gravili
9df6a644c9 chore: update lockfile 2024-04-23 16:37:02 -04:00
Alessio Gravili
1a6d9eaa11 Merge remote-tracking branch 'origin/beta' into fix/lexical-localization 2024-04-23 16:33:54 -04:00
Alessio Gravili
7d447af277 chore: add remaining missing preferences prop to validations 2024-04-23 15:46:01 -04:00
Elliot DeNolf
d8baaab849 chore(release): v3.0.0-beta.14 [skip ci] 2024-04-23 15:29:35 -04:00
Jarrod Flesch
3e1523f007 fix: move graphql-http from devDep to dep in next package (#5982)
Co-authored-by: Elliot DeNolf <denolfe@gmail.com>
2024-04-23 15:27:43 -04:00
Alessio Gravili
fa38af025f Merge branch 'beta' into fix/lexical-localization 2024-04-23 15:20:56 -04:00
Elliot DeNolf
6ca9ff847f chore(release): v3.0.0-beta.13 [skip ci] 2024-04-23 15:15:21 -04:00
Alessio Gravili
a8824b2b51 fix: incorrect value for empty preferences passed into buildStateFromSchema 2024-04-23 15:12:55 -04:00
Alessio Gravili
6aa3752b16 feat(richtext-lexical): allow richtext adapters to hook into field hooks 2024-04-23 15:10:35 -04:00
Elliot DeNolf
c483a439bf build: adjust pnpm engines version 2024-04-23 15:01:00 -04:00
Jarrod Flesch
74bdf1c681 chore: reduces graphql dependencies (#5979)
Co-authored-by: Elliot DeNolf <denolfe@gmail.com>
2024-04-23 15:00:09 -04:00
James Mikrut
7437d9fe58 Fix/postgres relation names (#5976) 2024-04-23 14:56:43 -04:00
Elliot DeNolf
51f7351962 fix(cpa): install db adapter in package.json (#5921) 2024-04-23 14:03:01 -04:00
Elliot DeNolf
d01fcb921b chore: tsconfig.json back to default 2024-04-23 13:18:45 -04:00
Elliot DeNolf
6179c938bf ci: remove email e2e tests, ethereal calls failing 2024-04-23 13:18:10 -04:00
Elliot DeNolf
dbbcb658a9 fix(deps): proper deps for storage-s3 and storage-vercel-blob (#5975) 2024-04-23 13:17:00 -04:00
James
16f97ad7c3 chore: disables forced pg for tests 2024-04-23 13:13:59 -04:00
James
bc7445ed99 Merge branch 'beta' of github.com:payloadcms/payload into fix/postgres-relation-names 2024-04-23 12:43:32 -04:00
James
e4d024cd0d chore: properly destroys db in postgres 2024-04-23 12:43:25 -04:00
James
1005de8295 fix(db-postgres): shortens relation names 2024-04-23 12:14:01 -04:00
Elliot DeNolf
c79289cedf chore(release): v3.0.0-beta.12 [skip ci] (#5972) 2024-04-23 11:02:08 -04:00
Jarrod Flesch
6a745be036 chore: pass mock req through with validate function to slate richText validation function (#5971) 2024-04-23 10:57:36 -04:00
Elliot DeNolf
cee9cc33ed chore(release): v3.0.0-beta.12 [skip ci] 2024-04-23 10:55:51 -04:00
Elliot DeNolf
9a5e9313cd ci: remove warning for no artifacts found 2024-04-23 10:47:17 -04:00
Elliot DeNolf
5401af5812 chore(storage-*): set disableLocalStorage true for enabled collections (#5970) 2024-04-23 10:46:33 -04:00
Elliot DeNolf
6305a1d1c2 chore: remove NodemailerAdapter type imports 2024-04-23 10:09:35 -04:00
Jarrod Flesch
95b96e3e9e chore: adjust headersWithCors for req without payload (#5963) 2024-04-23 09:50:41 -04:00
Elliot DeNolf
95b3f6d40d chore(scripts): add new packages to getPackageRegistryVersions 2024-04-23 09:10:25 -04:00
Elliot DeNolf
c258a4bef1 chore(scripts): add throttling to release script, optional git commit arg 2024-04-23 09:09:55 -04:00
Elliot DeNolf
647544a0c6 chore: fix build:tests filter [skip ci] 2024-04-23 08:46:15 -04:00
Elliot DeNolf
7e0a2a879c chore: adjust nodemailer type export 2024-04-23 08:39:32 -04:00
Elliot DeNolf
471e1388ae ci: bump pnpm version in gh action, use variable 2024-04-22 22:29:07 -04:00
Elliot DeNolf
1da430b042 ci: bump pnpm version 2024-04-22 22:01:56 -04:00
Elliot DeNolf
56ac06c563 fix: disallow importing from ts extensions 2024-04-22 21:15:13 -04:00
Elliot DeNolf
4dec4bb61c fix: resave media using cloud storage plugin (#5959) 2024-04-22 19:58:57 -04:00
Elliot DeNolf
99a09c49a3 ci: start docker for plugin-cloud-storage e2e 2024-04-22 16:59:57 -04:00
James Mikrut
88fd46bfea fix(db-postgres): row table names were not being built properly (#5960) 2024-04-22 16:55:12 -04:00
Elliot DeNolf
8a6603b3d8 test: add plugin-cloud-storage e2e 2024-04-22 16:43:54 -04:00
PatrikKozak
f6c9f454a5 Merge branch 'beta' of https://github.com/payloadcms/payload into fix/row-table-names 2024-04-22 16:18:24 -04:00
PatrikKozak
d8a5426c37 chore: adds array within row in tabsDoc data 2024-04-22 16:18:14 -04:00
Elliot DeNolf
c9011dcbfd fix(plugin-cloud-storage): resave media 2024-04-22 16:11:19 -04:00
Jarrod Flesch
43089fd13c chore: adds cors headers to routeErrors (#5957) 2024-04-22 15:48:42 -04:00
Elliot DeNolf
bb3bd9c395 chore: adjust email adapter messaging 2024-04-22 15:42:21 -04:00
James
ba423ab424 fix: row table names were not being built properly 2024-04-22 15:10:59 -04:00
Elliot DeNolf
c23984cac3 feat(plugin-cloud-storage): implement storage packages (#5928) 2024-04-22 14:31:20 -04:00
Elliot DeNolf
6685a0fa7e feat!: email adapter (#5901) 2024-04-22 14:26:12 -04:00
Jarrod Flesch
ac4750d016 chore: adds fallbackFileType functionality (#5958) 2024-04-22 14:20:02 -04:00
Elliot DeNolf
951e9fd7f2 test: email e2e updated nodemailer usage 2024-04-22 14:13:38 -04:00
Elliot DeNolf
cbd1554589 chore: adjust email pattern 2024-04-22 13:32:33 -04:00
Jacob Fletcher
80c545933f fix(next): adds CORS headers to API Responses (#5906)
Co-authored-by: Jarrod Flesch <jarrodmflesch@gmail.com>
2024-04-22 12:13:06 -04:00
Paul
594f319fc6 chore!: admin now takes a client side custom property and custom is server only (#5926) 2024-04-22 12:22:32 -03:00
Elliot DeNolf
102feb9576 chore: updates telemetry (#5883) 2024-04-22 09:44:34 -04:00
Simon Vreman
68274d2862 fix(plugin-cloud-storage)!: Pass filename to utility function getting file prefix (#5934) 2024-04-21 07:32:11 -04:00
Dan Ribbens
8945b7a4fa fix(db-postgres): nested groups in nested blocks validation (#5941)
Co-authored-by: Ricardo Domingues <rfdomingues98@gmail.com>
2024-04-21 00:38:55 -04:00
Dan Ribbens
d5ef93b2ba fix(db-postgres): v3 #5938 extra version suffix table names (#5940) 2024-04-20 23:23:06 -04:00
Ritsu
cb0f0dba3a chore: removes comment and unused type import (#5935) 2024-04-20 23:06:43 -04:00
Paul
7b263be01b chore: add missing translations (#5929) 2024-04-20 14:57:22 -04:00
Dan Ribbens
56df60f520 chore: fixes e2e test running on windows (#5927) 2024-04-20 14:54:18 -04:00
Ritsu
d5cbbc472d feat: add count operation to collections (#5930) 2024-04-20 14:45:44 -04:00
Dan Ribbens
d987e5628a feat(live-preview-vue): new live-preview-vue package (#5933)
Co-authored-by: Christian Gil <mrcgam.christian@gmail.com>
2024-04-20 07:52:00 -04:00
Dan Ribbens
1383191f15 fix: v3 update many with drafts (#5900)
Co-authored-by: Elliot DeNolf <denolfe@gmail.com>
2024-04-19 16:32:59 -04:00
Ritsu
27297284cf fix: Passes correct path to import modules on Windows started with file:// (#5919) 2024-04-19 16:28:41 -04:00
Kendell Joseph
3af3a91c87 feat: json field schemas (#5898) 2024-04-19 13:35:59 -04:00
Paul
23c5b71f95 chore(payload,ui)!:update custom config to separate client and server bundles (#5914) 2024-04-19 11:52:55 -03:00
Dan Ribbens
2ee6a8ec3a fix(db-mongodb): ignore end session errors (#5905) 2024-04-19 09:19:55 -04:00
Elliot DeNolf
10819b8693 chore: proper SendMailOptions export 2024-04-18 15:43:10 -04:00
Elliot DeNolf
83c617b452 test: clean up email-nodemailer config 2024-04-18 14:17:36 -04:00
Elliot DeNolf
4acb133655 chore: export SendMailOptions 2024-04-18 14:15:55 -04:00
Elliot DeNolf
6e4135e790 test: add nodemailer adapter to email test config 2024-04-18 13:36:48 -04:00
Elliot DeNolf
f0198b62f3 feat: implement stdout email adapter, use if no adapter configured 2024-04-18 11:59:03 -04:00
Jessica Chowdhury
3ff8063ab8 chore:(i18n): adds translation for document/s key (#5890) 2024-04-18 10:18:06 +01:00
Elliot DeNolf
8d52f1b279 chore: add payload to dev deps 2024-04-18 02:27:43 -04:00
Elliot DeNolf
24072d222c chore: clean up types, remove logMockEmailCredentials 2024-04-18 02:07:54 -04:00
Elliot DeNolf
55c59e71da chore: remove nodemailer from payload completely 2024-04-18 01:44:35 -04:00
Elliot DeNolf
62233788e0 feat(plugin-cloud): use nodemailer adapter 2024-04-18 01:44:20 -04:00
Elliot DeNolf
b297c5499d chore(email): strict true 2024-04-18 00:02:05 -04:00
Elliot DeNolf
fb7925f272 feat: create email-nodemailer package 2024-04-17 21:58:24 -04:00
Patrik
221e873862 chore(translations): adds localsNotSaved_one & localsNotSaved_other translations (#5903) 2024-04-17 16:34:10 -04:00
Patrik
e7143e02e2 fix: adds type error validations for email and password in login operation (#5899) 2024-04-17 16:33:19 -04:00
Elliot DeNolf
a1d68bd951 feat: abstract nodemailer into email adapter interface 2024-04-17 16:10:51 -04:00
Jarrod Flesch
93ee452a2d fix(next): do not require handlers, attempt to read filesystem or throw (#5896) 2024-04-17 15:12:57 -04:00
Jarrod Flesch
1abaa5fc17 chore(next): bump next@^14.3.0-canary.7 (#5894) 2024-04-17 13:07:40 -04:00
Alessio Gravili
999059bc61 fix(richtext-lexical): properly validate block node nested fields, fixes one failing e2e test suite we previously skipped 2024-04-17 11:47:24 -04:00
Alessio Gravili
39ba39c237 feat(richtext-lexical)!: rework how population works and saves data, improve node typing 2024-04-17 11:46:47 -04:00
Jarrod Flesch
009e6c2066 chore(test): fix flakey relationship tests (#5892) 2024-04-17 11:44:07 -04:00
Ritsu
8bf03ae706 fix(next): pass a corrent content-type header in getFile route (#5799)
Co-authored-by: Jarrod Flesch <jarrodmflesch@gmail.com>
2024-04-17 11:40:02 -04:00
Kendell Joseph
a2fe3f66e3 fix: accepts empty cell data in json field (#5876) 2024-04-17 11:07:28 -04:00
Jacob Fletcher
6cd5b253f1 fix(next): admin access control (#5887) 2024-04-17 10:31:39 -04:00
Elliot DeNolf
abf0461d80 ci: add exports pattern to codeowners 2024-04-17 10:24:02 -04:00
Elliot DeNolf
abca45e152 chore: add comments to exports about server vs. front-end 2024-04-17 10:23:21 -04:00
Alessio Gravili
58ea94f6ac feat: pass through doc preferences to field validate function and improve types 2024-04-17 09:27:04 -04:00
Jessica Chowdhury
49cba92fa1 fix(create-payload-app): uses baseUrl for payload config path in tsconfig (#5888) 2024-04-17 13:53:00 +01:00
Elliot DeNolf
42329fc736 ci: cut down on codeowners noise [skip ci] 2024-04-16 22:07:55 -04:00
Elliot DeNolf
68dee49501 feat(cpa): list plugin template after updating for 3.0 2024-04-16 19:46:27 -04:00
Dan Ribbens
234837ee1d fix: postgres query hasMany in (#5884) 2024-04-16 17:09:43 -04:00
Kendell Joseph
0d3554d70a fix: accepts empty cell data 2024-04-16 12:35:03 -04:00
Paul
7f6c6c4787 fix(next): check for matching passwords when creating the first user (#5869) 2024-04-16 12:41:20 -03:00
Jacob Fletcher
b6c975bfdc Revert "fix(plugin-seo): uses correct key for ukrainian translation"
This reverts commit b9a9dad60a.
2024-04-16 11:36:38 -04:00
Elliot DeNolf
eaf5a86121 ci: enforce node version for all jobs 2024-04-16 11:35:00 -04:00
Jacob Fletcher
b9a9dad60a fix(plugin-seo): uses correct key for ukrainian translation 2024-04-16 11:14:44 -04:00
Alessio Gravili
a2afc38894 fix(richtext-lexical): do not allow empty url field in link drawer 2024-04-16 11:03:12 -04:00
Paul
b80c92ba93 fix(next): issue with password and confirm password fields not being type of password (#5870) 2024-04-16 11:52:56 -03:00
Patrik
6669a2cedb fix(next): adds client-side field validations to login and forgot-password views (#5871) 2024-04-16 10:36:37 -04:00
Bohdan Kucheriavyi
7369da3d8d chore(plugin-seo): adds Ukrainian translations (#5836)
Signed-off-by: Bohdan Kucheriavyi <bohdan.kucheriavyi@zapal.tech>
2024-04-16 09:32:08 -04:00
Jarrod Flesch
697a0f1ecf fix: ensure body limit is respected (#5807)
Co-authored-by: James <james@trbl.design>
2024-04-16 09:22:41 -04:00
Jarrod Flesch
3db0557b07 chore: improve cookie helper functions (#5866) 2024-04-15 22:11:17 -04:00
Elliot DeNolf
8178d57ab9 chore(release): v3.0.0-beta.11 [skip ci] 2024-04-15 16:50:51 -04:00
Ritsu
f1b2f767bb fix(db-postgres): validateExistingBlockIsIdentical localized (#5840) 2024-04-15 16:50:28 -04:00
Dan Ribbens
f21b394d21 chore(create-payload-app): db user and password connection URI (#5853) 2024-04-15 16:40:31 -04:00
Dan Ribbens
4e4ccca02a fix(db-mongodb): version fields indexSortableFields (#5864) 2024-04-15 16:26:26 -04:00
Elliot DeNolf
b6578d6447 test(pcs): add prefix test (#5867) 2024-04-15 16:10:51 -04:00
Elliot DeNolf
abeb94a53d docs: add externalFileHeaderFilter 2024-04-15 15:11:35 -04:00
Ritsu
974a74500b fix(ui): passes cellComponentProps through buildColumnState (#5848) 2024-04-15 15:03:16 -04:00
Elliot DeNolf
61dd17ae5e feat: allow configuration for setting headers on external file fetch (#5862) 2024-04-15 15:02:07 -04:00
Jacob Fletcher
2628249a51 fix(next): removes links to hidden entities (#5861) 2024-04-15 14:58:57 -04:00
Elliot DeNolf
5f57782199 fix(db-postgres): properly pass id type for type gen (#5859) 2024-04-15 13:38:46 -04:00
Ritsu
bceb49ee6c fix(ui): ensures titleField is not empty (#5850) 2024-04-15 13:33:49 -03:00
Alessio Gravili
beeb59f263 ci: add weird tune linux network step which seems to reduce flakes (#5855) 2024-04-15 12:23:48 -04:00
Paul
4150c87be0 chore(plugin-nested-docs): update nested docs plugin exports and moved away from default exports (#5856) 2024-04-15 13:22:01 -03:00
Patrik
a394d8211e fix: passes parent id instead of incoming id to saveVersion (#5854) 2024-04-15 12:02:17 -04:00
Paul
6a162776f2 chore: export react toastify from UI (#5828) 2024-04-15 12:53:12 -03:00
James Mikrut
d41bd7b133 chore: exports getFieldsToSign (#5852) 2024-04-15 10:48:03 -04:00
Patrik
f3409fab29 fix(db-mongodb): failing contains query with special chars (#5776) 2024-04-15 10:24:07 -04:00
James Mikrut
dd75fbfee2 feat: image dimensions rework (#5824) 2024-04-15 10:22:40 -04:00
James
ae2c85f947 chore: exports getFieldsToSign 2024-04-15 10:19:55 -04:00
Oladayo Olufemi Fagbemi
c0b454a5de fix(plugin-seo): add default empty endpoints array (#5844) 2024-04-14 17:44:15 -04:00
Alessio Gravili
27754dd0d7 fix(richtext-lexical): ensure schema maps for complex fields / sub-fields are handled correctly (#5842) 2024-04-14 17:29:48 -04:00
Oladayo Olufemi Fagbemi
18ec830882 fix(plugin-seo): incorrect styling of field labels (#5845)
Co-authored-by: Oladayo Fagbemi <oladayo.fagbemi@acelspringer.com>
Co-authored-by: Alessio Gravili <alessio@gravili.de>
2024-04-14 17:28:53 -04:00
Ritsu
1ffc0f552e fix(payload): remove incorrect payload module import within payload (#5847) 2024-04-14 16:49:06 -04:00
Alessio Gravili
07b676ac81 chore(richtext-lexical): adjust field name inside int tests 2024-04-14 16:45:54 -04:00
Alessio Gravili
da79f09544 fix(payload): ensure that the minimum @swc/core peerdep version used is 1.4.13 (#5841) 2024-04-14 16:43:42 -04:00
Alessio Gravili
993b035285 fix(richtext-lexical): ensure schema maps for complex fields / sub-fields are handled correctly for blocks, link and upload features 2024-04-14 02:19:58 -04:00
Alessio Gravili
3f2df643e7 chore(richtext-lexical): add failing e2e test which ensures sub-richtext blocks work as intended 2024-04-14 02:18:16 -04:00
James
42c7649176 chore: removes tempy 2024-04-13 17:06:25 -04:00
Ritsu
2722d2f5ce fix: passes number type limit arg to find on the list view (#5837)
Co-authored-by: Paul <paul@payloadcms.com>
2024-04-13 17:24:46 -03:00
Elliot DeNolf
f71e61d7d9 chore(templates): remove no longer used editor-import comment 2024-04-13 10:27:39 -04:00
Gabriel Novotny
73c76cab77 fix: postgres turbopack static analysis error (#5832) 2024-04-12 18:53:19 -04:00
Elliot DeNolf
43e8a533b7 fix: ensure file persists through form state changes (#5823) 2024-04-12 15:16:47 -04:00
Elliot DeNolf
ea4203bb32 chore(release): v3.0.0-beta.10 [skip ci] 2024-04-12 14:58:12 -04:00
Elliot DeNolf
568b5073c8 chore(deps): sync pnpm-lock.yaml 2024-04-12 14:56:42 -04:00
Elliot DeNolf
471e1f4827 chore: specify next canary peer dep 2024-04-12 14:56:04 -04:00
Elliot DeNolf
b9185a6fcd chore: fix more publish config exports 2024-04-12 14:48:34 -04:00
Elliot DeNolf
80496aa94c fix: remove all exports null (#5830) 2024-04-12 14:35:36 -04:00
Alessio Gravili
5fd6e3c1a8 fix!: upgrade minimum required node version from 18.17.0 to v18.20.2. Some old node versions have issues with our loader (#5829) 2024-04-12 14:01:33 -04:00
Alessio Gravili
54590c1700 fix(richtext-lexical)!: fix output of internal list HTML converter (#5827)
BREAKING: Changes the classnames of the converted HTML
2024-04-12 12:10:44 -04:00
Elliot DeNolf
b1259be8f2 chore(release): v3.0.0-beta.9 [skip ci] 2024-04-12 12:06:41 -04:00
Elliot DeNolf
cd161e4b16 feat!: remove pointer files (#5826) 2024-04-12 11:58:34 -04:00
Alessio Gravili
cb4214fe6e fix(richtext-lexical)!: fix output of internal list HTML converter
BREAKING: Changes the classnames of the converted HTML
2024-04-12 11:58:05 -04:00
Elliot DeNolf
9d42751a42 feat!: remove more pointer files 2024-04-12 11:39:08 -04:00
Elliot DeNolf
c2c637b359 chore: clean up unused files 2024-04-12 11:35:25 -04:00
Paul
2f446e11d6 chore: bump nextjs dependencies to ^14.2 (#5820)
fix(plugin-seo): overriding existing endpoints
2024-04-12 12:32:45 -03:00
Elliot DeNolf
4f566b088c feat!: remove pointer files 2024-04-12 11:31:35 -04:00
Dan Ribbens
0d40d87b31 fix(db-postgres): relationship query pagination (#5803) 2024-04-12 11:18:40 -04:00
Elliot DeNolf
70fcd6bf40 feat: rework image dimensions, use image-size 2024-04-12 11:13:16 -04:00
Jacob Fletcher
94c0095b3b chore(ui): removes all static font assets (#5821) 2024-04-12 09:57:31 -04:00
Elliot DeNolf
4328060637 feat(pcs): vercel blob storage adapter (#5811) 2024-04-12 09:37:35 -04:00
Elliot DeNolf
d98d0fd5bd chore(pcs): use proper getFilePrefix 2024-04-12 09:30:19 -04:00
Elliot DeNolf
5db2863d08 feat(pcs): export utilities 2024-04-12 09:30:10 -04:00
Jacob Fletcher
ff5e438d6d chore(ui): replaces suisse-intl font with system fallbacks 2024-04-12 09:16:43 -04:00
Paul
bfd5f13ee9 chore: add types for local api find/update operations (#5808) 2024-04-12 10:15:57 -03:00
Elliot DeNolf
8043188f36 chore(pcs): update README 2024-04-12 09:12:34 -04:00
Elliot DeNolf
e3e0998772 chore: ignore vercelBlob pointers, adjust peer deps 2024-04-12 00:34:41 -04:00
Elliot DeNolf
86adc6f282 chore: add vercelBlob to exports 2024-04-11 23:05:22 -04:00
Elliot DeNolf
b51b519d30 feat(plugin-cloud-storage): vercel blob storage adapter 2024-04-11 22:58:55 -04:00
Alessio Gravili
c70dcb6a59 feat(richtext-lexical): add HorizontalRuleFeature, improve block handle positioning (#5806) 2024-04-11 16:51:54 -04:00
Alessio Gravili
2486c7dba0 fix(richtext-lexical): incorrect margin for nested unordered lists 2024-04-11 16:42:35 -04:00
Paul
1456fcdcad chore: type locale from localization config on the payload request (#5801) 2024-04-11 17:38:35 -03:00
Alessio Gravili
a216800c72 chore(richtext-lexical): fix build error 2024-04-11 16:31:40 -04:00
Alessio Gravili
c3d8597c13 feat(richtext-lexical): add HorizontalRuleFeature 2024-04-11 16:24:04 -04:00
Alessio Gravili
844663ce1a fix(richtext-lexical): limit unnecessary floating handle positioning updates 2024-04-11 15:55:55 -04:00
Alessio Gravili
055e6af7b7 feat(richtext-lexical): improve floating handle y-positioning by positioning it in the center for smaller elements. 2024-04-11 15:55:43 -04:00
Alessio Gravili
479e6ecddc fix(richtext-lexical): incorrect floating handle y-position calculation next to certain kinds of HTML elements like HR 2024-04-11 15:55:26 -04:00
Jacob Fletcher
b9456e8244 fix(next): safely handles missing json body in post requests (#5797) 2024-04-11 15:53:50 -04:00
Jacob Fletcher
432dfef435 chore(next): installs merriweather as google font and removes static assets 2024-04-11 15:31:19 -04:00
Elliot DeNolf
429c6f7a48 chore(release): v3.0.0-beta.6 [skip ci] 2024-04-11 15:20:10 -04:00
Elliot DeNolf
6d41f6c56d fix(ui): actual scss paths [skip ci] 2024-04-11 15:17:33 -04:00
Elliot DeNolf
20ac2b86cf chore(release): v3.0.0-beta.5 [skip ci] 2024-04-11 15:11:14 -04:00
Elliot DeNolf
b88455166a fix: improve config finding (#5800) 2024-04-11 15:08:04 -04:00
Elliot DeNolf
9b86de1f9d fix(ui): scss paths 2024-04-11 15:06:27 -04:00
Elliot DeNolf
1393c72281 fix: improve config finding 2024-04-11 15:06:09 -04:00
Jacob Fletcher
bcb538aee2 fix(next): awaits logout operation in api route handler 2024-04-11 14:57:35 -04:00
Jacob Fletcher
01e8f8c649 Merge branch 'beta' into fix/post-body-parse 2024-04-11 14:15:56 -04:00
Elliot DeNolf
1119cf3af9 chore(release): v3.0.0-beta.4 [skip ci] 2024-04-11 14:02:17 -04:00
Elliot DeNolf
216934145c fix: default baseUrl in loader if not set (#5798) 2024-04-11 13:52:35 -04:00
James Mikrut
40f952cac3 chore: type local API auth function and PayloadRequest (#5791) 2024-04-11 13:45:24 -04:00
Jacob Fletcher
330e4a7724 fix(next): safely handles missing json body in post requests 2024-04-11 13:42:21 -04:00
Paul Popus
e676503e02 update further types 2024-04-11 14:12:03 -03:00
James Mikrut
06233fbb2f fix: Generated ids for an array items are the same as global id if it's created (#5795) 2024-04-11 13:06:24 -04:00
Paul Popus
17298695b1 fix: provide request to the previewFunction and fix type 2024-04-11 14:01:22 -03:00
Jarrod Flesch
c1081ccfe2 chore(tests): flakey drawer, tab, navigation tests (#5792) 2024-04-11 12:57:19 -04:00
Ritsu
30da5a8643 fix: generated ids for array items the same as global id 2024-04-11 19:46:14 +03:00
Paul Popus
55bf5436e4 fix type issues 2024-04-11 13:09:39 -03:00
Elliot DeNolf
a4956dc649 fix(cpa): ast parse error handling (#5793) 2024-04-11 12:01:16 -04:00
James Mikrut
512b7bd429 fix: number ids were not sanitized to number in rest api (#5778) 2024-04-11 11:19:22 -04:00
Paul Popus
5119c51439 chore: type request too 2024-04-11 12:19:11 -03:00
James
f3e25f3277 more de-flake 2024-04-11 11:17:59 -04:00
Jacob Fletcher
1275c70187 feat(ui): provides payload as prop to all custom server components (#5775) 2024-04-11 11:16:15 -04:00
James
be69fc448d chore: de-flake 2024-04-11 10:59:26 -04:00
Paul Popus
bcccefe98e chore: add int test for local API login function 2024-04-11 11:56:52 -03:00
Paul Popus
2061f38d9e feat: add new type generation for the auth operation 2024-04-11 11:56:42 -03:00
Elliot DeNolf
d0869d9087 chore: unused file [skip ci] 2024-04-11 10:22:06 -04:00
Elliot DeNolf
1eabf316d6 chore(templates): add generate:types to blank 2024-04-10 22:30:56 -04:00
Elliot DeNolf
a0dd750a52 chore(cpa): add or operator to process.env.DATABASE_URI [skip ci] 2024-04-10 22:17:52 -04:00
Elliot DeNolf
2fc9885abc chore(scripts): add getPackageRegistryVersions script 2024-04-10 21:26:50 -04:00
Elliot DeNolf
14d683fb9a chore(scripts): update release script 2024-04-10 21:26:31 -04:00
Elliot DeNolf
e286519cb1 chore(release): v3.0.0-beta.3 [skip ci] 2024-04-10 20:46:22 -04:00
Elliot DeNolf
b1e78a3562 feat(cpa): improvements (#5783) 2024-04-10 20:35:19 -04:00
Elliot DeNolf
0bc103658a chore(cpa): improve move message 2024-04-10 20:29:57 -04:00
Elliot DeNolf
9037b9b4fa chore(cpa): remove 2.0 templates until updated 2024-04-10 20:25:20 -04:00
Elliot DeNolf
d194493e9a chore(cpa): update help 2024-04-10 20:24:51 -04:00
Elliot DeNolf
aa22344cdb fix(cpa): append to existing .env 2024-04-10 20:16:59 -04:00
Elliot DeNolf
736e7b822e chore(release): v3.0.0-beta.2 [skip ci] 2024-04-10 17:32:32 -04:00
Elliot DeNolf
2ebda95036 fix(next): proper named export of withPayload 2024-04-10 17:30:47 -04:00
Elliot DeNolf
d8783eaad4 chore(release): v3.0.0-beta.1 [skip ci] 2024-04-10 17:16:20 -04:00
Elliot DeNolf
cddb08de1a chore: ignore payload/i18n 2024-04-10 17:11:20 -04:00
James
d4e5d3df54 chore: fixes to unit tests 2024-04-10 17:05:44 -04:00
James
414b03ce74 chore: fix to unit tests 2024-04-10 17:03:37 -04:00
James
96dbab8834 chore: misc fixes 2024-04-10 16:58:08 -04:00
Elliot DeNolf
03a110a750 feat(next)!: cjs support (#5772) 2024-04-10 16:44:20 -04:00
Elliot DeNolf
6accc705be chore(next): cjs build 2024-04-10 16:32:36 -04:00
Elliot DeNolf
312dca003b feat(cpa): CJS next config AST parsing 2024-04-10 16:32:19 -04:00
James
4f9fdb6c14 fix: number ids were not sanitized to number in rest api 2024-04-10 16:01:28 -04:00
Jarrod Flesch
94af06466b chore: re-exports languages in payload (#5771) 2024-04-10 15:55:01 -04:00
Jacob Fletcher
7cf2686097 fix: optionally types req in auth operation (#5769) 2024-04-10 14:08:47 -04:00
Elliot DeNolf
f14883aa11 chore: update blank 3.0 template for withPayload change 2024-04-10 13:41:11 -04:00
Elliot DeNolf
9df8de2386 chore: adjust exports 2024-04-10 13:41:11 -04:00
James
8b2cf4705e chore: withPayload CJS 2024-04-10 13:41:10 -04:00
Jarrod Flesch
364e9832ac fix(next): ensures requested lang header is supported (#5765) 2024-04-10 13:05:56 -04:00
Jarrod Flesch
2deeb61f17 fix: locale switcher flakey test (#5761) 2024-04-10 13:05:30 -04:00
Patrik
14498e8a9c fix(ui): avoids getting and setting doc preferences when creating new (#5758) 2024-04-10 11:42:03 -04:00
Alessio Gravili
eb78022387 fix: undo changing baseBlockFields type to FieldWithRichTextRequiredEditor (#5767) 2024-04-10 11:38:27 -04:00
Elliot DeNolf
3677a59a78 fix(cpa): dependency tag (#5768) 2024-04-10 11:25:54 -04:00
Alessio Gravili
7c1c840a59 chore: increase admin e2e beforeAll timeout, as prebuild sometimes takes longer than the timeout 2024-04-10 11:22:17 -04:00
Alessio Gravili
a73eaf5d37 chore: fix test suite types, add LexicalBlock type 2024-04-10 11:07:01 -04:00
Alessio Gravili
68989a58a8 fix: undo changing baseBlockFields types to FieldWithRichTextRequiredEditor 2024-04-10 10:56:07 -04:00
Alessio Gravili
9841731ae7 feat: properly type withPayload (#5756) 2024-04-10 09:51:46 -04:00
Elliot DeNolf
ba7ac5d439 test: fix unit tests (#5760) 2024-04-09 23:08:20 -04:00
Elliot DeNolf
7c60772b26 ci: alpha -> beta branch push list 2024-04-09 23:07:09 -04:00
Alessio Gravili
1141a5d3af fix(richtext-lexical): do not render uploads extra fields drawer if no extra fields are provided (#5755) 2024-04-09 16:30:43 -04:00
Elliot DeNolf
6f74fd1f98 chore(release): v3.0.0-beta.0 [skip ci] 2024-04-09 15:00:39 -04:00
Alessio Gravili
75873bfcfa fix(richtext-lexical): catch errors that may occur during HTML generation (#5752) 2024-04-09 14:53:17 -04:00
Jarrod Flesch
1faf621f17 fix: persist locale when navigating (#5753) 2024-04-09 14:49:26 -04:00
Elliot DeNolf
1d1c73dfcc chore(release): v3.0.0-alpha.61 [skip ci] 2024-04-09 14:36:04 -04:00
Patrik
d057ce0a85 fix(next): removes global slug from collectionSlug prop in SetStepNav (#5744) 2024-04-09 14:26:09 -04:00
James Mikrut
0ce26d2c08 Feat/config i18n (#5735) 2024-04-09 14:13:17 -04:00
Alessio Gravili
abf285d713 chore: get dev:generate-types to work again (#5750) 2024-04-09 14:12:23 -04:00
James
c2ee8e3999 chore: de-flakes fields/index tests 2024-04-09 13:56:32 -04:00
James
167ba0c68f chore: adds back all tests 2024-04-09 13:50:54 -04:00
James
9ad1cbe920 chore: adjusts test snapshot logic to restore uploads properly 2024-04-09 13:33:42 -04:00
James
313ea52e3d chore: getRequestLanguage now defaults 2024-04-09 13:17:43 -04:00
James
3acfb7a83f chore: corrects imports 2024-04-09 12:43:36 -04:00
James
783dae2bbb Merge branch 'alpha' of github.com:payloadcms/payload into feat/config-i18n 2024-04-09 12:35:57 -04:00
Alessio Gravili
59681b211b fix(richtext-lexical): upload nodes weren't visible (#5746) 2024-04-09 12:32:24 -04:00
James
98438175cf chore: handles server errors 2024-04-09 12:31:25 -04:00
Alessio Gravili
af40302e5f fix(richtext-lexical): get links to work again (#5745) 2024-04-09 12:29:45 -04:00
Alessio Gravili
ec0e0ae449 chore(richtext-lexical): add e2e test to ensure that pre-seeded upload nodes are visible 2024-04-09 12:24:54 -04:00
Alessio Gravili
607ff17033 fix(richtext-lexical): upload nodes weren't visible due to incorrect relationships condition 2024-04-09 12:24:30 -04:00
Alessio Gravili
e73e610669 chore: fields test suite: clearAndSeedEverything instead of seed for dev as well, to ensure same state as test runs (most importantly, this gets rid of leftover uploads) 2024-04-09 12:22:42 -04:00
Jarrod Flesch
30fddde066 chore: corrects type for getLocalI18n 2024-04-09 11:51:40 -04:00
Jarrod Flesch
a56d2842fb chore: converts ua to uk 2024-04-09 11:44:34 -04:00
Jarrod Flesch
35f59a47cc chore: corrects dateFNS keys, stricter types 2024-04-09 11:38:38 -04:00
Jarrod Flesch
817d57bd12 chore: migrate langs 2024-04-09 11:05:35 -04:00
Elliot DeNolf
5826048e7b ci: publish script throttling 2024-04-09 09:50:23 -04:00
Elliot DeNolf
1a975b31cf chore(release): v3.0.0-alpha.60 [skip ci] 2024-04-09 09:36:41 -04:00
James
73298a80f0 chore: adds more de-flake to tabs 2024-04-09 09:34:11 -04:00
James
a5d14ef4c1 chore: de-flakes tabs 2024-04-09 09:34:11 -04:00
James
d0c79b65f8 chore: skips index to see what remains 2024-04-09 09:34:11 -04:00
James
2fc50b1a1f chore: de-flakes array 2024-04-09 09:34:11 -04:00
James
0ddeedb0b3 chore: de-flakes relationship suite 2024-04-09 09:34:11 -04:00
James
09c2fb10f3 chore: bug in ci 2024-04-09 09:34:11 -04:00
James
5bfff5b7ba chore: ensures artifacts work 2024-04-09 09:34:11 -04:00
James
702088375c chore: attempts to de-flake 2024-04-09 09:34:11 -04:00
James
ea507fbcc4 chore: moves lexical tests into collection folder 2024-04-09 09:34:11 -04:00
James
0ff1e6632b chore: splits out relationship 2024-04-09 09:34:11 -04:00
James
996ee47f96 chore: splits out blocks and array into their own suites 2024-04-09 09:34:11 -04:00
James
3e9bd5bb62 chore: uses ci env var to set max retries 2024-04-09 09:34:11 -04:00
James
ee7221c986 chore: sets maxRetries 2024-04-09 09:34:11 -04:00
James
318c126ae3 chore: turns off prebuild for fields 2024-04-09 09:34:11 -04:00
Jarrod Flesch
2154aea89f chore: comment out all other test suites for now 2024-04-09 09:34:11 -04:00
Jarrod Flesch
4d3ad1af35 chore: run fields e2e on ci 2024-04-09 09:34:11 -04:00
Alessio Gravili
75cab7688f chore: fix incorrect next tsconfig paths breaking monorepo setup (#5743) 2024-04-09 09:26:09 -04:00
James
5084d6dd97 chore: attempts to de-flake live preview 2024-04-08 22:34:23 -04:00
James
518f80cbb6 chore: es 2024-04-08 22:29:41 -04:00
James
c9399efa65 chore: fixes access control test 2024-04-08 22:25:35 -04:00
Elliot DeNolf
dd9133659c ci: rework caching, consolidates build (#5737) 2024-04-08 22:19:37 -04:00
James
d3016b7eb5 chore: merge 2024-04-08 22:13:16 -04:00
James
69e884f5b7 chore: dynamically loads date-fns locales 2024-04-08 22:12:05 -04:00
Elliot DeNolf
6d122905f4 chore: ignore new pointer files 2024-04-08 21:19:03 -04:00
Elliot DeNolf
e6e016ac2d chore(cpa): build for es6 (#5736) 2024-04-08 17:11:03 -04:00
James
3e7925e33f chore: removes async nature from a few lexical things 2024-04-08 16:48:56 -04:00
James
7a2ccba63c Merge branch 'feat/config-i18n' of github.com:payloadcms/payload into feat/config-i18n 2024-04-08 16:39:03 -04:00
James
be2134eb69 chore: requires languages to be passed to config 2024-04-08 16:38:12 -04:00
James Mikrut
95e422b0e1 Merge branch 'alpha' into feat/config-i18n 2024-04-08 16:26:38 -04:00
James
53d9c4ca95 chore: dynamically loads translations 2024-04-08 16:25:24 -04:00
James
30948ab545 chore: dynamically loads translations 2024-04-08 16:25:21 -04:00
Jacob Fletcher
906df6b401 fix(next): properly renders document-level unauthorized view (#5734) 2024-04-08 15:40:46 -04:00
Jacob Fletcher
b9c585bab5 fix(ui): uses correct save draft button label (#5730) 2024-04-08 14:34:28 -04:00
Elliot DeNolf
12203140ad chore: unprettified pointer files 2024-04-08 13:59:03 -04:00
Patrik
0704152e38 chore(next): removes unnecessary apostrophe from payload-lng cookie (#5729) 2024-04-08 13:42:44 -04:00
Elliot DeNolf
f582efe98d chore(release): v3.0.0-alpha.59 [skip ci] 2024-04-08 13:32:21 -04:00
James Mikrut
540579f520 chore: corrects invalid export (#5728) 2024-04-08 13:26:37 -04:00
James
24c348dc49 chore: corrects invalid export 2024-04-08 13:26:04 -04:00
Jacob Fletcher
4b4c245507 fix(ui): properly initializes collapsible context (#5725) 2024-04-08 12:48:44 -04:00
Dan Ribbens
400f68d1aa chore: add fetch to dev.js to trigger admin (#5724) 2024-04-08 12:26:33 -04:00
Jacob Fletcher
5bb27ed9cd fix(ui): renders searchable fields in list controls (#5723) 2024-04-08 11:49:02 -04:00
Jacob Fletcher
833498c269 chore(deps): regenerates frozen lockfile 2024-04-08 11:30:12 -04:00
Jacob Fletcher
80507d487b Merge branch 'alpha' into fix/list-searchable-fields 2024-04-08 11:23:24 -04:00
Jacob Fletcher
08f4ebaaf8 fix(ui): adds css specificity to select all 2024-04-08 11:22:05 -04:00
Jacob Fletcher
4c418525eb fix(ui): renders searchable fields in list controls 2024-04-08 11:01:47 -04:00
Elliot DeNolf
4962f6c926 chore(release): v3.0.0-alpha.58 [skip ci] 2024-04-08 10:52:04 -04:00
James Mikrut
50f0e9298c chore: adds upload export back (#5722) 2024-04-08 10:47:10 -04:00
James
89efcc5db1 chore: adds upload export back 2024-04-08 10:46:41 -04:00
Elliot DeNolf
c3119a5632 chore(release): v3.0.0-alpha.57 [skip ci] 2024-04-08 10:29:26 -04:00
James Mikrut
069bbd92b0 Feat/bulletproof loader (#5721) 2024-04-08 10:18:38 -04:00
James
c4422a2593 chore: improves logic of loader 2024-04-08 10:17:18 -04:00
James
3aab9d368e chore: working loader 2024-04-08 10:07:55 -04:00
Elliot DeNolf
b6afab63b2 chore(cpa): remove test from prepublishOnly 2024-04-08 09:34:52 -04:00
Alessio Gravili
b93f5e9c44 feat!: pass in req to access.admin, to match 2.0 behavior, and strongly type it (#5712) 2024-04-07 22:22:48 -04:00
James
630082035f chore: sets up test environment for loader 2024-04-07 19:48:42 -04:00
Elliot DeNolf
c74e41fc76 chore(release): v3.0.0-alpha.56 [skip ci] 2024-04-07 13:53:49 -04:00
Alessio Gravili
08ce7c58b5 chore: upgrade playwright & TS, and hide deprecation warnings (#5708) 2024-04-06 17:15:02 -04:00
Alessio Gravili
1853fde379 chore: upgrade typescript and "@types/"-prefixed packages 2024-04-06 15:35:09 -04:00
Alessio Gravili
f29d22ca95 chore: ensure node deprecation warnings stay hidden during e2e test runs 2024-04-06 15:06:04 -04:00
Alessio Gravili
5ea5f928ab chore: upgrade playwright from 1.42.1 to 1.43.0 and update patches 2024-04-06 15:05:27 -04:00
James Mikrut
efd6d35eb3 Fix/live preview flake (#5707) 2024-04-06 14:31:19 -04:00
James
2b2538f13a chore: removes old bin 2024-04-06 14:31:02 -04:00
James
9375dae179 Merge branch 'alpha' of github.com:payloadcms/payload into fix/live-preview-flake 2024-04-06 14:22:50 -04:00
James
674bb3758d chore: reduces parallel creates in live preview seed 2024-04-06 14:22:43 -04:00
James Mikrut
cedf9a2eb8 chore: pre-builds in CI (#5690) 2024-04-06 14:10:25 -04:00
James
7d2dc5b6c6 chore: adds more tests to loader int suite 2024-04-06 14:07:17 -04:00
James
9d2aad7bf9 chore: lockfile 2024-04-06 14:01:16 -04:00
James
f085d7609b chore: cleanup 2024-04-06 13:59:29 -04:00
James
a49243a42a chore: cleanup 2024-04-06 13:58:05 -04:00
James
e79f431f14 chore: cleans up tsconfigs 2024-04-06 13:40:30 -04:00
James
38e5b6e8e3 chore: temp disables fields 2024-04-06 13:38:14 -04:00
James
35bdb785c4 Merge branch 'chore/pre-build-e2e' of github.com:payloadcms/payload into chore/pre-build-e2e 2024-04-06 13:35:35 -04:00
James
25cb146fde feat: moduleResolution: bundler in config loaders 2024-04-06 13:35:09 -04:00
Jarrod Flesch
c10f0f4a9e chore: fix sort header route replace 2024-04-05 22:34:04 -04:00
Elliot DeNolf
3a3a7f6e16 ci(scripts): remove p-map 2024-04-05 17:48:21 -04:00
Elliot DeNolf
1d3b500962 chore(release): v3.0.0-alpha.55 [skip ci] 2024-04-05 17:40:07 -04:00
Dan Ribbens
f39f95af6d feat: custom db naming for postgres and mongodb (#5697) 2024-04-05 17:09:08 -04:00
Dan Ribbens
6fec2bbe1c chore: refactor auth from next into new payload local operation (#5641) 2024-04-05 17:08:51 -04:00
Elliot DeNolf
2412134073 chore: importConfig and importWithoutClientFiles (#5701) 2024-04-05 16:49:20 -04:00
Dan Ribbens
136545d1fd feat: move disableDuplicate out of admin and update APIs (#5620) 2024-04-05 16:44:41 -04:00
Jarrod Flesch
684c4d2113 chore: fix admin sort flake, fix console warning for test package.json 2024-04-05 16:39:13 -04:00
Elliot DeNolf
6624fd0401 ci: single job per branch (#5702) 2024-04-05 16:24:53 -04:00
James
31502d2da3 chore: attempts to de-flake admin 2024-04-05 16:12:49 -04:00
James
3ee39ecca3 chore: increases timeout for fields prebuild 2024-04-05 16:06:00 -04:00
Paul
89e7c305e7 chore: emails e2e suite (#5698)
fix: move 'email' configuration to server only
2024-04-05 16:50:47 -03:00
James
60dd71c59e chore: sets longer timeout for prebuild in admin and fields 2024-04-05 15:49:12 -04:00
James
0936f77930 Merge branch 'alpha' of github.com:payloadcms/payload into chore/pre-build-e2e 2024-04-05 15:25:41 -04:00
James
3c09b95a8c chore: builds in child process for e2e 2024-04-05 15:23:07 -04:00
Jacob Fletcher
780f26f135 fix(next): incorrect access of select options in versions view (#5687) 2024-04-05 15:03:49 -04:00
Jacob Fletcher
77618674a0 fix(next): live preview url (#5692) 2024-04-05 15:00:58 -04:00
Alessio Gravili
c9c89a6005 fix(richtext-lexical): do not allow omitting editor prop for sub-richtext fields within lexical defined in the payload config (#5699) 2024-04-05 14:42:15 -04:00
Jacob Fletcher
d1276c4299 feat(next): threads payload through live preview url 2024-04-05 14:36:14 -04:00
Jacob Fletcher
69730b6c95 Merge branch 'alpha' into fix/lp-url 2024-04-05 14:25:17 -04:00
James
9147d30152 Merge branch 'chore/green-ci' of github.com:payloadcms/payload into chore/pre-build-e2e 2024-04-05 14:13:28 -04:00
James
6217c70fb5 chore: prebuilds fields and admin in ci 2024-04-05 14:13:14 -04:00
Jarrod Flesch
17352c9a56 chore: change pw buttons, field readOnly prop, admin panel thumbnailURL fallback (#5694) 2024-04-05 13:51:17 -04:00
Jacob Fletcher
1b6026304f fix(next): adjusts args sent through live preview url 2024-04-05 13:37:12 -04:00
Jacob Fletcher
91f9973c6c fix(next): properly types locale in live preview url 2024-04-05 13:37:07 -04:00
Elliot DeNolf
ca2acee38a chore(release): create-payload-app/v3.0.0-alpha.54 [skip ci] 2024-04-05 11:53:19 -04:00
James
3bd455ced2 chore: pre-builds in CI 2024-04-05 11:46:02 -04:00
Patrik
2c25abd143 test(versions): replaces outdated CustomPublishButtonProps type (#5688) 2024-04-05 11:11:16 -04:00
PatrikKozak
08a9fb8cd7 Merge branch 'alpha' of https://github.com/payloadcms/payload into fix/alpha/versions-select-options 2024-04-05 10:38:23 -04:00
PatrikKozak
d8a38b4e35 fix: incorrect access of select options in versions 2024-04-05 10:38:09 -04:00
Paul
e64660f2d8 fix(db-postgres): querying by localised relations postgres (#5686) 2024-04-05 10:37:58 -04:00
Elliot DeNolf
1a11466e69 chore(release): v3.0.0-alpha.54 [skip ci] 2024-04-04 21:05:18 -04:00
Elliot DeNolf
78ab2fbe09 chore: adjust translations and next publishConfig 2024-04-04 21:03:36 -04:00
James
0c45d5773a chore: brings back admin and fields 2024-04-04 21:02:31 -04:00
James
f48335444b chore: flake 2024-04-04 21:01:38 -04:00
Elliot DeNolf
81b33cee5c chore(release): v3.0.0-alpha.53 [skip ci] 2024-04-04 20:32:31 -04:00
James Mikrut
020dcaad75 chore: exports sql from pg adapter (#5678) 2024-04-04 20:31:14 -04:00
James
9bc56bcfc7 chore: exports sql from pg adapter 2024-04-04 20:30:38 -04:00
Elliot DeNolf
8325fadeb3 chore(release): v3.0.0-alpha.52 [skip ci] 2024-04-04 20:15:31 -04:00
Elliot DeNolf
d54275b3bd chore: unpushed version bump 2024-04-04 20:11:16 -04:00
James Mikrut
29d20423a3 chore: adds retryWrites, fixes a few flakes (#5674) 2024-04-04 20:09:51 -04:00
James
e539816253 chore: temp remove fields 2024-04-04 20:09:33 -04:00
James
922ce9ef5f chore: re-enables database adapter types 2024-04-04 20:07:59 -04:00
James
b73ec6ae94 chore: flake 2024-04-04 19:39:41 -04:00
Elliot DeNolf
4a11bf956d fix: db migrations esm part 2 (#5677) 2024-04-04 19:36:59 -04:00
Elliot DeNolf
3b3bb6c80a fix: db migrations esm (#5675) 2024-04-04 19:33:04 -04:00
James
0f323ff2e3 chore: re-adds fields 2024-04-04 19:17:41 -04:00
James
3305c65ae6 chore: adds retryWrites, fixes a few flakes 2024-04-04 19:14:01 -04:00
Elliot DeNolf
5c5acdcb03 chore(release): v3.0.0-alpha.50 [skip ci] 2024-04-04 19:00:11 -04:00
James Mikrut
0e6991f486 chore: green ci (#5673) 2024-04-04 18:44:27 -04:00
James
014786cc5f chore: green ci 2024-04-04 18:44:04 -04:00
James Mikrut
223c6b50fc Fix/fields e2e (#5672) 2024-04-04 18:24:21 -04:00
James
b18946352e chore: misc fields e2e fixes 2024-04-04 18:23:31 -04:00
James
96012b26b7 chore: properly isolates req in parallel requests 2024-04-04 18:00:37 -04:00
James
31cd663ad5 chore: merge alpha 2024-04-04 17:33:30 -04:00
Jacob Fletcher
0e9c9d7ccf Fix/admin e2e (#5670) 2024-04-04 17:31:42 -04:00
James
2a6eb6ec86 Merge branch 'fix/fields-e2e' of github.com:payloadcms/payload into fix/fields-e2e 2024-04-04 17:25:21 -04:00
James
0a74423c07 chore: replaces clearAndSeedEverything 2024-04-04 17:25:09 -04:00
Alessio Gravili
6f1302ae67 chore: skip react-select flakester test 2024-04-04 17:23:07 -04:00
Alessio Gravili
f275570f12 fix: WhereBuilder Relationship placeholder not shown if value is cleared 2024-04-04 17:18:16 -04:00
Alessio Gravili
90b47f6c44 Merge remote-tracking branch 'origin/fix/fields-e2e' into fix/fields-e2e 2024-04-04 17:10:14 -04:00
Alessio Gravili
e73d008695 fix: WhereBuilder Relationship conditions weren't able to be removed 2024-04-04 17:09:58 -04:00
Jacob Fletcher
3c5d1b402c test(admin): enables entity descriptions test 2024-04-04 17:09:22 -04:00
Jacob Fletcher
3e22bccce4 fix(ui): ssr entity descriptions 2024-04-04 17:08:03 -04:00
Jacob Fletcher
354e140305 fix(ui): ssr entity descriptions 2024-04-04 17:07:09 -04:00
James
54859f3582 Merge branch 'fix/fields-e2e' of github.com:payloadcms/payload into fix/fields-e2e 2024-04-04 17:04:35 -04:00
James
da12efd675 chore: more passing fields e2e 2024-04-04 17:04:23 -04:00
Alessio Gravili
32f848e90d fix: correctly extract relationTo prop for Relationship field condition in WhereBuilder 2024-04-04 16:55:46 -04:00
Jacob Fletcher
32440d23f7 chore(ui): extracts collection and global component maps into standalone files 2024-04-04 16:40:58 -04:00
Jarrod Flesch
62b7acc93a chore: simplify usage of useTitle 2024-04-04 16:15:49 -04:00
Jacob Fletcher
7b8e2c75c2 ci: enables admin e2e test suite 2024-04-04 16:09:46 -04:00
Jacob Fletcher
03abc641c5 test(admin): ignores search params when waiting for list view url 2024-04-04 16:09:46 -04:00
Alessio Gravili
fa5b98c9b5 chore: wait through e2e flakes 2024-04-04 15:59:48 -04:00
Alessio Gravili
c681be7ba8 chore: reduce flakes in fields/uploads e2e test suite 2024-04-04 15:17:52 -04:00
Alessio Gravili
3d3305a312 Merge remote-tracking branch 'origin/alpha' into fix/fields-e2e 2024-04-04 14:57:22 -04:00
Alessio Gravili
bb305af7b4 chore: fields-relationship e2e fixes (#5665) 2024-04-04 14:53:16 -04:00
Alessio Gravili
fd284973b6 ci: add missing --with-deps to playwright install command (#5667) 2024-04-04 14:52:59 -04:00
James
5d57572694 chore: work to add consistency to fields e2e 2024-04-04 14:41:58 -04:00
Jarrod Flesch
36a22f2b3c chore: fixes failing where builder 2024-04-04 14:28:47 -04:00
Patrik
dea9b590d1 fix: incorrect tooltip colors in light mode (#5636) 2024-04-04 14:13:25 -04:00
Patrik
bf843fe598 fix: skip parsing if operator is exists (#5639) 2024-04-04 14:11:22 -04:00
Jacob Fletcher
a23bc6caa8 Merge pull request #5646 from payloadcms/fix/alpha/admin-e2e
Fix/alpha/admin e2e
2024-04-04 14:10:21 -04:00
Alessio Gravili
1904fd5b02 chore: commit intellij payload.iml and mark temp or non-core directories as excluded. This excludes them from search (#5662) 2024-04-04 14:03:44 -04:00
Jacob Fletcher
03c9a883e1 Merge branch 'alpha' into fix/alpha/admin-e2e 2024-04-04 13:48:11 -04:00
Jacob Fletcher
c06df267a3 test(live-preview): uses correct locator to find linked cell 2024-04-04 13:42:55 -04:00
Jacob Fletcher
0bccdfeda7 fix(ui): finds index of useAsTitle after mutating column order 2024-04-04 13:42:55 -04:00
Jarrod Flesch
07d118ae7d chore: fix filtering tests 2024-04-04 13:39:17 -04:00
Alessio Gravili
e912dde08d chore: ensure autologin passes before starting tests for all e2e test suites (#5659) 2024-04-04 13:39:06 -04:00
Elliot DeNolf
3544375fdd chore(deps): remove release-it (#5658) 2024-04-04 12:57:13 -04:00
Elliot DeNolf
ab6ca7910e ci: release script concurrency (#5657)
* chore(deps): update turborepo

* chore: unprettified upload pointer files

* ci: use p-map in release script
2024-04-04 12:37:05 -04:00
Jacob Fletcher
6a329f7a8e fix(ui): adds optional chaining to fieldComponentProps in buildColumnState 2024-04-04 12:23:31 -04:00
Elliot DeNolf
5d4bb10106 chore: update all package.json repository urls and homepage (#5655) 2024-04-04 12:02:08 -04:00
Jarrod Flesch
cbc079bfff Merge branch 'fix/alpha/fields-e2et push' into fix/alpha/admin-e2e 2024-04-04 11:58:21 -04:00
Jarrod Flesch
09358d5853 chore: fix customLabel inside buildColumnState 2024-04-04 11:42:48 -04:00
Jacob Fletcher
81c345f33e test(admin): extracts api view tests into standalone group 2024-04-04 11:25:53 -04:00
Jacob Fletcher
3aa200eacc chore(deps): removes react-router-dom from peer deps 2024-04-04 11:24:09 -04:00
James Mikrut
6ce0b60cf2 Merge pull request #5652 from payloadcms/fix/ensure-indexes
chore: re-enables fields-relationship tests
2024-04-04 11:23:45 -04:00
James
faef0784ee chore: re-enables fields-relationship tests 2024-04-04 11:22:39 -04:00
Jarrod Flesch
fb70fe5760 chore: skips i18n tests for now 2024-04-04 11:09:31 -04:00
Jarrod Flesch
5b75b8a89e chore: fixes multi-select stalling tests 2024-04-04 11:04:46 -04:00
Jarrod Flesch
fa0296b796 chore: passing admin/list-filtering/multi-select 2024-04-04 11:04:46 -04:00
Jarrod Flesch
4e2d1f568f chore: fix plugin-form-builder test 2024-04-04 11:04:46 -04:00
Jacob Fletcher
b8d1aec1e5 fix(ui): properly sanitizes functional labels from field map 2024-04-04 10:53:04 -04:00
James Mikrut
38cfd6985e Merge pull request #5651 from payloadcms/fix/ensure-indexes
chore: ensure indexes are created before running localization test suite
2024-04-04 10:22:02 -04:00
James
d7c20c6941 chore: ensure indexes are created before running localization test suite 2024-04-04 10:11:45 -04:00
Jacob Fletcher
7894a54a0e Merge branch 'alpha' into fix/alpha/admin-e2e 2024-04-04 10:02:37 -04:00
James Mikrut
a46e64eec3 Merge pull request #5650 from payloadcms/fix/e2e-flakes
chore: adds logging, converts localization to use no config
2024-04-04 09:59:47 -04:00
Jarrod Flesch
cae0399584 chore: adds types to default columns 2024-04-04 09:54:05 -04:00
Jacob Fletcher
bfbf4ef0b5 test(admin): improves file organization and test names 2024-04-04 09:52:51 -04:00
Jarrod Flesch
ca4004605e chore: adds types to default columns 2024-04-04 09:52:21 -04:00
James
ec565a1bd3 chore: adds update to test sdk 2024-04-04 09:47:08 -04:00
Jarrod Flesch
94c4b180c1 chore: fix tsc errors 2024-04-04 09:40:36 -04:00
James
66de0b9019 chore: adds logging, converts localization to use no config 2024-04-04 09:34:45 -04:00
Jacob Fletcher
f752b38228 test(admin): partially passing list view filtering 2024-04-04 09:30:30 -04:00
Jarrod Flesch
d79748a967 chore: more admin test fixes 2024-04-04 09:28:12 -04:00
Alessio Gravili
c1b6c2c5a5 chore: unflake versions e2e (#5616) 2024-04-04 09:07:44 -04:00
Alessio Gravili
806b04e0ca Merge pull request #5633 from payloadcms/feat/form-server-validation 2024-04-04 09:07:05 -04:00
Alessio Gravili
736b562f3a chore: clean-up console logs 2024-04-04 09:06:09 -04:00
James
db440236fc Merge branch 'feat/form-server-validation' of github.com:payloadcms/payload into feat/form-server-validation 2024-04-04 09:04:30 -04:00
James
00ea8b900a chore: skips bulk edit test until fixes are merged 2024-04-04 09:04:14 -04:00
Paul
e092e9ba67 fix: missing data in first user registration (#5645) 2024-04-03 19:08:10 -03:00
Jacob Fletcher
8313cf34a6 test(admin): passing custom css 2024-04-03 18:03:10 -04:00
Jacob Fletcher
8f4b8f5826 fix(ui): shares sort order between where builder and column selector 2024-04-03 17:31:57 -04:00
Jacob Fletcher
c607b01f33 chore(ui): extracts reduceFieldMap from where builder 2024-04-03 17:31:47 -04:00
Jarrod Flesch
cc5d01d0e2 chore: fixes deleteAllPosts test helper 2024-04-03 17:07:45 -04:00
Alessio Gravili
a4cc41b679 chore: skip more flaky tests 2024-04-03 17:04:26 -04:00
Jarrod Flesch
67361e9ed5 chore: fixes bulk upldate test 2024-04-03 16:56:54 -04:00
Alessio Gravili
8662572690 chore: enable fields on CI 2024-04-03 16:54:27 -04:00
Alessio Gravili
99ea1788e7 chore: skip flaky fields-relationship tests for now 2024-04-03 16:54:07 -04:00
James
7bec3c90cd chore: adds ux de-flaking to relationship field 2024-04-03 16:14:01 -04:00
Jacob Fletcher
ca4e6c46bc fix(ui): properly sorts table columns 2024-04-03 15:09:38 -04:00
James
678da159a9 Merge branch 'alpha' of github.com:payloadcms/payload into feat/form-server-validation 2024-04-03 14:20:39 -04:00
James
cc3b51fb3b chore: logs to localization seed 2024-04-03 14:18:36 -04:00
James
060344bb5a chore: logs for ci visibility 2024-04-03 14:09:42 -04:00
Jacob Fletcher
b42c67040d fix(ui): properly flattens tabs field map 2024-04-03 14:01:21 -04:00
Alessio Gravili
f3b18fcf0e chore: move autosave new document creation & redirect logic to server, fixes versions e2e flakes due to late redirect 2024-04-03 13:50:52 -04:00
Elliot DeNolf
a86c69edc9 fix: sets beforeValidateHook req type to required (#5634)
Co-authored-by: Patrik <patrik@payloadcms.com>
2024-04-03 13:45:44 -04:00
Jessica Chowdhury
55c60a05dc feat(plugin-seo): adds Norwegian translation (#5629) 2024-04-03 13:26:44 -04:00
Jacob Fletcher
ecf40cc747 fix(ui): renders field description functions 2024-04-03 13:18:49 -04:00
Jacob Fletcher
197458e60b chore: adds isReactComponent utility 2024-04-03 13:18:49 -04:00
Alessio Gravili
0c3ffb0743 chore: expect accurate error in uploads e2e test 2024-04-03 13:14:38 -04:00
Alessio Gravili
56dffd3c58 fix: validation not running correctly when changing field state and submitting form immediately after 2024-04-03 13:00:23 -04:00
Jarrod Flesch
57a3a37fdd chore: passing admin/i18n tests 2024-04-03 12:40:41 -04:00
Elliot DeNolf
a8a273f0d8 chore(cpa): ensure project name is slugified (#5631) 2024-04-03 12:18:46 -04:00
Jarrod Flesch
9f4ab26696 chore: passing admin/nav tests 2024-04-03 12:17:48 -04:00
James
4ddef9d648 chore: adds prop to form to allow server validation only on submit 2024-04-03 12:03:05 -04:00
Jessica Chowdhury
7cccca8194 chore(alpha): update fields-relationship e2e tests (#5553)
* fix: handles filter options in form state merge

* chore: fix and reintegrate fields-relationship e2e tests

* chore: update withMergedProps function for e2e tests
2024-04-03 16:11:19 +01:00
James Mikrut
2a8b678a4b Merge pull request #5604 from payloadcms/fix/postgres-fields
chore: revisions to baseIDField
2024-04-03 10:55:43 -04:00
Jarrod Flesch
b9767b865a chore: working but wip admin e2e 2024-04-03 10:47:01 -04:00
James
f6bc3eb014 chore: passing pg 2024-04-03 10:39:38 -04:00
Elliot DeNolf
007917df19 ci: docker compose v2, v1 no longer supported on gh runner (#5626) 2024-04-03 10:34:49 -04:00
Alessio Gravili
22a2e850bf Merge pull request #5627 from payloadcms/chore/port-lexical-fixes 2024-04-03 09:56:36 -04:00
Alessio Gravili
1cfdf3613c docs(richtext-lexical): clarify that HTML generation has to happen on the server 2024-04-03 09:55:33 -04:00
Alessio Gravili
fe280e6bb1 fix(richtext-lexical): disable instanceof HTMLImageElement check as it causes issues when used on the server 2024-04-03 09:55:23 -04:00
Jarrod Flesch
a330fe6017 chore(ui): simplifies adminThumbnail functionality (#5615) 2024-04-03 08:49:31 -04:00
Jessica Chowdhury
4ee4ad25b0 fix(alpha): number field with hasMany accept defaultValue array (#5619) 2024-04-03 08:05:49 -04:00
Paul
777a661389 chore: remove dead refresh permissions test from github workflows (#5614) 2024-04-02 20:57:54 -03:00
Paul
8174230afe chore: plugin form builder e2e (#5612)
* chore: update plugin files to esm

* chore: add e2e for plugin form builder

* chore: update release script and gh workflow

* chore: update build command for form builder plugin
2024-04-02 20:56:37 -03:00
Alessio Gravili
e1777dc533 ci: do not fail upload-artifact action if more than 2 e2e tests fail (#5605) 2024-04-02 18:36:07 -04:00
Elliot DeNolf
390731c07b ci: separate tests-unit, esm scripts (#5607)
* ci: script esm updates

* ci: separate out unit tests
2024-04-02 17:31:38 -04:00
James
25d475e165 chore: revisions to baseIDField 2024-04-02 17:04:27 -04:00
Alessio Gravili
1e60250670 chore: enable all fields int tests (#5603) 2024-04-02 17:00:32 -04:00
Alessio Gravili
f6d2dd520c Merge pull request #5561 from payloadcms/temp38
fix: test suite & transactions fixes
2024-04-02 16:00:10 -04:00
Alessio Gravili
4600588e72 chore: disable uploads e2e 2024-04-02 15:59:25 -04:00
Dan Ribbens
825ca94080 fix: duplicate handles locales with unique (#5600)
* fix: duplicate errors with localized and unique fields

* docs: beforeDuplicate hooks
2024-04-02 15:30:49 -04:00
Alessio Gravili
7f674f9861 chore: fix payload HMR being run during e2e & int tests 2024-04-02 15:01:18 -04:00
James
27dba7e4e1 chore: improper expect in upload test suite 2024-04-02 14:21:58 -04:00
Alessio Gravili
44295ff248 chore: use initPayloadInt consistently in all int test suites and do not init payload twice 2024-04-02 13:39:01 -04:00
Alessio Gravili
dc33d96a54 chore: remove async seeding from auth and fields-relationship test suites 2024-04-02 13:25:12 -04:00
Alessio Gravili
4ff7619356 chore: remove console logs 2024-04-02 12:29:38 -04:00
Alessio Gravili
cc5c2bd7cd chore: fix flakes in versions test suite 2024-04-02 12:27:29 -04:00
Alessio Gravili
2884712685 chore: fix versions int test seeding 2024-04-02 12:17:27 -04:00
Alessio Gravili
027264588b chore: versions test improvements 2024-04-02 12:13:12 -04:00
Alessio Gravili
ddd75ce730 Merge remote-tracking branch 'origin/temp38' into temp38 2024-04-02 12:12:51 -04:00
Alessio Gravili
4bc13c28dd chore: passing live-preview 2024-04-02 12:12:42 -04:00
James
7a1db89a6e Merge branch 'temp38' of github.com:payloadcms/payload into temp38 2024-04-02 12:12:35 -04:00
James
c08489509a chore: startMemoryDB in e2e 2024-04-02 12:12:21 -04:00
Alessio Gravili
7054ae8a88 chore: unit/int test CI stuff 2024-04-02 12:00:32 -04:00
Alessio Gravili
d7e913be95 fix: do not re-use same transaction ID for parallel operations 2024-04-02 11:08:04 -04:00
James
f283a2ced5 Merge branch 'temp38' of github.com:payloadcms/payload into temp38 2024-04-02 10:40:06 -04:00
James
c7274ba16f chore: wires up conditions for collapsibles, groups, etc 2024-04-02 10:39:52 -04:00
Alessio Gravili
6f323e379c add console logs 2024-04-02 10:35:20 -04:00
Alessio Gravili
42212b409a chore: remove console log 2024-04-02 10:19:24 -04:00
James
e8506cc5f1 chore: startMemoryDB pointing to mongodb instead of mongoose 2024-04-02 10:02:51 -04:00
James
d387f9f1fa chore: working pattern for debugging e2e and int 2024-04-02 10:01:47 -04:00
James
73a555788d chore: uses globalSetup for starting memory db 2024-04-02 09:44:55 -04:00
Elliot DeNolf
be58f67115 test(uploads): remove all process.cwd() usage (#5588) 2024-04-02 00:08:58 -04:00
Elliot DeNolf
b26117a65d feat(cpa): strict true 😈 (#5587) 2024-04-01 23:05:57 -04:00
Alessio Gravili
34fe6182c8 temp3 2024-04-01 23:05:54 -04:00
Alessio Gravili
ee3ae6025f temp2 2024-04-01 22:41:24 -04:00
James
df9812b2a3 Merge branch 'temp38' of github.com:payloadcms/payload into temp38 2024-04-01 22:13:02 -04:00
James
113eea04cc chore: seed endpoint 2024-04-01 22:12:45 -04:00
Alessio Gravili
57f9ebdb68 temp1 2024-04-01 22:04:45 -04:00
James
94d0e28ad7 chore: local api sdk for e2e tests 2024-04-01 21:53:30 -04:00
James
cd553d45cc chore: merge 2024-04-01 17:47:28 -04:00
James
d993f9ac64 chore: bug in isMongoose 2024-04-01 17:46:42 -04:00
James
833bdc13bd chore: uploads e2e tweak 2024-04-01 17:38:24 -04:00
James Mikrut
33657b4b49 Merge branch 'alpha' into temp38 2024-04-01 17:37:54 -04:00
James
ec6bc8e36b chore: removes old refs to startMemoryDB 2024-04-01 17:36:36 -04:00
Jacob Fletcher
799370f753 fix(next): establishes pattern for preview urls (#5581) 2024-04-01 17:30:49 -04:00
James
abd404c57c chore: adjusts playwright env used to trigger memory db 2024-04-01 17:30:18 -04:00
Kendell Joseph
037ed3cd54 test: e2e uploads (#5511)
* chore: enables upload tests on CI

* fix: adds relationTo information to field map

* chore: updates e2e tests (WIP)

* chore: move back to probe-image-size, tiff files do not support buffers

* chore: basic runtime err fixes

* chore: remove admin thumbnail when creating client config

* test: small upload fixes

---------

Co-authored-by: Elliot DeNolf <denolfe@gmail.com>
Co-authored-by: Jarrod Flesch <jarrodmflesch@gmail.com>
2024-04-01 17:28:15 -04:00
James
c461a7fa15 chore: renames mongoose db adapter refs to mongodb 2024-04-01 17:24:43 -04:00
James
8fc8aaa6dd yammil 2024-04-01 17:14:43 -04:00
James
2bc45e2b2e chore: adds logging for ci 2024-04-01 17:12:16 -04:00
James
cdfc58d115 chore: re-adds int 2024-04-01 17:08:50 -04:00
James
bb8a57d2e9 chore: better pattern to initialize memory server 2024-04-01 17:04:05 -04:00
James
5e52339135 chore: converts e2e suites to new pattern 2024-04-01 16:37:12 -04:00
James
df75914e30 chore: attempts to get _community to pass with change to import order of config 2024-04-01 16:29:22 -04:00
Patrik
572e6ccb37 fix(ui): places id field last in field map and prevents render (#5585) 2024-04-01 16:25:29 -04:00
James
8e1ebe28c0 chore: adds node env for e2e tests 2024-04-01 15:57:23 -04:00
James
adec044e02 chore: returns runE2E 2024-04-01 15:55:38 -04:00
James
b9868cc709 chore: reverts memory test approach 2024-04-01 15:51:40 -04:00
James
fce8b125f8 Merge branch 'temp38' of github.com:payloadcms/payload into temp38 2024-04-01 15:29:43 -04:00
James
4befd2e4ff chore: sets env vars for tests in globalSetup 2024-04-01 15:29:27 -04:00
James Mikrut
38cdc1b7ba Merge branch 'alpha' into temp38 2024-04-01 14:37:33 -04:00
James
a0f6018469 chore: better pattern for memory db 2024-04-01 14:36:08 -04:00
James
f230d55031 chore: restores memory db 2024-04-01 11:11:52 -04:00
James
2f6a15a9ae chore: calculates default values before running buildFormState 2024-04-01 10:52:26 -04:00
Elliot DeNolf
04d751208f Merge pull request #5557 from payloadcms/feat/cpa-detect-next-app
feat(cpa): detect next app
2024-04-01 10:44:43 -04:00
Elliot DeNolf
7cfc40f328 test(cpa): update tests 2024-04-01 10:32:17 -04:00
Elliot DeNolf
3c54d32b6d feat(cpa): rework all prompts to use @clack/prompts 2024-04-01 10:16:07 -04:00
Jessica Chowdhury
ece7d92e57 chore: updates e2e tests for plugin-nested-docs and plugin-seo (#5434)
* test: removes unnecessary lines

* fix: do not error if row field has no fields (#5433)

* ci(deps): update turborepo

* ci: release script updates

* chore: lint all json/yml, add to lint-staged

* chore: lint mdx in lint-staged

* chore: enable e2e live preview (#5444)

* chore: update workflow file

---------

Co-authored-by: Alessio Gravili <70709113+AlessioGr@users.noreply.github.com>
Co-authored-by: Elliot DeNolf <denolfe@gmail.com>
Co-authored-by: Paul <paul@payloadcms.com>
2024-04-01 15:01:05 +01:00
James
8e736b28af Merge branch 'temp38' of github.com:payloadcms/payload into temp38 2024-04-01 09:30:12 -04:00
Alessio Gravili
92ec0a5b1d chore: temporarily disable int tests, and field-error-states, live-preview, versions e2e's (#5578) 2024-03-31 22:41:52 -04:00
Alessio Gravili
398834f690 øMerge remote-tracking branch 'origin/alpha' into temp38 2024-03-31 21:35:54 -04:00
Alessio Gravili
28e6dd8759 fix: do not exclude admin.hidden fields from default active columns (#5556) 2024-03-31 21:35:11 -04:00
Alessio Gravili
dedc937915 Merge pull request #5576 from payloadcms/feat/alpha-5574
feat(richtext-*): add ability to provide custom Field and Error components
2024-03-31 21:17:19 -04:00
Alessio Gravili
732f4241fe chore: commit updated translation files 2024-03-31 21:16:17 -04:00
Alessio Gravili
873585e1ae feat(richtext-*): add ability to provide custom Field and Error components 2024-03-31 21:12:52 -04:00
Alessio Gravili
71a5a02e8c docs(richtext-slate): update outdated code example (#5572) (#5573) 2024-03-31 17:50:15 -04:00
Alessio Gravili
51fbd02b40 fix(richtext-lexical): checklist html converter incorrectly outputting children (#5570) (#5571) 2024-03-31 16:25:16 -04:00
Alessio Gravili
763eda5038 fix(richtext-lexical): properly center add- and drag-block handles (#5568) (#5569) 2024-03-31 16:08:00 -04:00
Alessio Gravili
6cdb76503b chore: disable memory db for now, as it doesn't work locally for the versions test suite 2024-03-31 16:05:55 -04:00
Alessio Gravili
aa8edd7a47 chore: fix issues in versions e2e test 2024-03-31 16:05:20 -04:00
Alessio Gravili
535aa56627 fix: do not pass undefined data through buildStateFromSchema for tab fields 2024-03-29 17:32:40 -04:00
Elliot DeNolf
db0fb30f7b test(cpa): update jest config 2024-03-29 17:28:35 -04:00
Elliot DeNolf
7619fb4753 feat(cpa): handle next.js app with and without src dir 2024-03-29 16:45:52 -04:00
Paul
0aeba954d4 fix: localization e2e (#5555)
* fix: issue with missing locale when duplication localized collections

* chore: fix localization tests
2024-03-29 17:45:26 -03:00
Alessio Gravili
2bc1468fa2 chore: tests: uploads dir: delete and restore snapshot between tests (#5560)
* chore: tests: uploads dir: delete and restore snapshot between tests

* chore: add missing creation of uploads dir cache folder

* fix logic
2024-03-29 16:29:40 -04:00
James Mikrut
4b29b6efc5 Merge pull request #5559 from payloadcms/temp36
Temp36
2024-03-29 14:57:27 -04:00
James
26b1003cfd Merge branch 'alpha' of github.com:payloadcms/payload into alpha 2024-03-29 14:56:15 -04:00
James
b696dce6e4 chore: disables password fields if disableLocalStrategy, safely inherits valid: false 2024-03-29 14:55:58 -04:00
Elliot DeNolf
403a86feca chore(create-payload-app): configure db in init next flow 2024-03-29 14:25:00 -04:00
Jacob Fletcher
56d6a9767e Merge pull request #5551 from payloadcms/fix/nav
fix: misc.
2024-03-29 13:58:22 -04:00
James
d2cc229622 Merge branch 'alpha' of github.com:payloadcms/payload into fix/alpha/rte-e2e-tests 2024-03-29 13:51:29 -04:00
Jacob Fletcher
e6b166da7d fix(next): proper 404 handling 2024-03-29 13:30:00 -04:00
Elliot DeNolf
77f401d977 chore(create-payload-app): console.log wrapper 2024-03-29 13:23:25 -04:00
Elliot DeNolf
7d7b232fdb feat(create-payload-app): functioning init next flow, no prompts 2024-03-29 12:48:00 -04:00
James
959f1e33cd chore: form now validates without field validate functions 2024-03-29 12:44:40 -04:00
Elliot DeNolf
7f5ab96f81 chore: move payload config into src 2024-03-29 12:22:32 -04:00
James
443089a66f chore: fixes Translation component 2024-03-29 11:54:20 -04:00
Jacob Fletcher
f5d9b47177 fix(richtext-lexical): uses entity visibility hook when enabling relationships 2024-03-29 11:54:02 -04:00
Jacob Fletcher
a0cddbe9b3 fix(richtext-slate): uses entity visibility hook when enabling relationships 2024-03-29 11:36:49 -04:00
Jacob Fletcher
5f7fcfd3df chore(next): uses visibileEntities in getViewsFromConfig 2024-03-29 11:30:33 -04:00
Alessio Gravili
a39080340a fix: missing translation key for richtext fields (#5550) 2024-03-29 11:11:50 -04:00
Jacob Fletcher
a3d6879c55 fix(ui): wraps nav with fragment 2024-03-29 11:10:43 -04:00
Alessio Gravili
b09be86a3c Merge pull request #5549 from payloadcms/temp33
fix: unload client functions after unmount (e.g. leaving document)
2024-03-29 11:07:47 -04:00
James
02ef033d23 chore: fixes infinite processing when submitting bad form 2024-03-29 11:03:21 -04:00
Alessio Gravili
f10861e1de chore: add lexical test which verifies it's working 2024-03-29 10:53:30 -04:00
James
ecf53d9961 chore: passes through props in listinfoprovider 2024-03-29 10:51:24 -04:00
Paul
5339c09b72 fix: access control test suite (#5548)
* chore: improve flakiness with access control test suite

* fix issue with redirecting from a drawer

* chore: watches for created id in drawers

---------

Co-authored-by: James <james@trbl.design>
2024-03-29 11:46:46 -03:00
Jacob Fletcher
b6ad218126 fix(ui): establishes pattern for hidden entities (#5546) 2024-03-29 10:32:09 -04:00
Alessio Gravili
b7b74a429e fix: unload client functions after unmount (e.g. leaving document) 2024-03-29 09:54:11 -04:00
James
52acd3123f chore: allows slate to render 2024-03-29 09:48:54 -04:00
Patrik
c9c3a689d8 fix: flaky indexed test suite (#5509) 2024-03-29 09:01:44 -04:00
Patrik
da4a2a2494 fix: reverts selector in array bulk update test to original to get passing test (#5512) 2024-03-29 09:00:48 -04:00
James
35a1fb26f9 Merge branch 'fix/alpha/rte-e2e-tests' of github.com:payloadcms/payload into fix/alpha/rte-e2e-tests 2024-03-29 08:27:00 -04:00
Jarrod Flesch
cb3723242c fix: passing versions e2e (#5521) 2024-03-29 01:20:02 -04:00
Patrik
6a0c6284d0 fix: passing blocks field test suite (#5529) 2024-03-28 20:56:52 -04:00
Jarrod Flesch
114ade7456 chore: wip rte field work 2024-03-28 16:53:32 -04:00
Kendell Joseph
a01e0e37f4 fix: corrects query so upload edits can happen (#5527) 2024-03-28 16:22:26 -04:00
Elliot DeNolf
18884025de ci: disable access-control suite until flakes are fixed 2024-03-28 15:41:20 -04:00
Alessio Gravili
cfdc941207 fix(richtext-lexical): Blocks: do not include empty arrays in form state (#5526) 2024-03-28 15:40:02 -04:00
Kendell Joseph
5eaf00ba0e chore: use dirname to resolve file locations for uploads test (#5523) 2024-03-28 15:35:35 -04:00
Elliot DeNolf
8948555ac1 ci: re-enable fields int suite (#5525) 2024-03-28 15:34:09 -04:00
Alessio Gravili
e28418d6d6 fix: onError not found error (#5524) 2024-03-28 15:10:20 -04:00
Kendell Joseph
fe83c53206 fix(ui): adds relationTo to cellComponentProps (#5518) 2024-03-28 14:43:46 -04:00
Alessio Gravili
942aa08285 chore: obliterate next cache before starting e2e/int/dev (#5522) 2024-03-28 14:42:45 -04:00
Alessio Gravili
93dd6b5a98 chore: add skipped, failing lexical e2e test for errors within nested block fields, fix lexical seed data, disable access-control test (#5508) 2024-03-28 13:36:05 -04:00
James Mikrut
08dd9ca91c Merge pull request #5516 from payloadcms/feat/simplify-doc-fetch
feat: simplifies fetching of docs for drawer and edit views
2024-03-28 13:35:07 -04:00
Jacob Fletcher
77efdc3ccf fix(ui): adds support for direct field label props (#5517) 2024-03-28 13:22:09 -04:00
James
ef1bcd5afa chore: fixes redirection on create 2024-03-28 13:18:35 -04:00
Jarrod Flesch
8636685252 fix: simplifies field error paths (#5504) 2024-03-28 13:15:44 -04:00
Patrik
5873dfb731 fix(ui): incorrect conditions in WhereBuilder (#5503)
* fix: relationship field tests e2e

* chore: adds POLL_TOPASS_TIMEOUT to relationship field tests

* chore: adds comments to relationship test waits
2024-03-28 13:14:00 -04:00
James
934abec88c feat: simplifies fetching of docs for drawer and edit views 2024-03-28 12:14:12 -04:00
Jarrod Flesch
c1d654c4ce fix(tests): passing tabs test in fields suite (#5463) 2024-03-28 11:28:37 -04:00
Paul
d64b12b14c fix: breadcrumbs on live preview tab (#5478) 2024-03-28 09:32:21 -03:00
Alessio Gravili
3f4ab5f95e fix: undefined error when creating new row without subFieldState present (#5502) 2024-03-27 17:13:06 -04:00
Jacob Fletcher
ee1e94be96 fix(ui): sets proper id on publish button and updates custom button semantics in field map (#5501) 2024-03-27 17:09:16 -04:00
Alessio Gravili
1e9999a8d3 fix: shallow-copy new form state one level deeper, fixes conditions (#5499) 2024-03-27 16:51:13 -04:00
Dan Ribbens
460ca99fe1 chore: alpha fix test defaultvalues (#5500) 2024-03-27 16:47:22 -04:00
Jacob Fletcher
7fdf9b7012 fix(ui): threads initial data through document info provider (#5498) 2024-03-27 16:39:00 -04:00
Alessio Gravili
c16b869fea Merge pull request #5491 from payloadcms/temp24
fix: form-state issues
2024-03-27 16:28:26 -04:00
Alessio Gravili
8deb19e3ac chore: replace incorrect usage of saveDocAndAssert helpers 2024-03-27 16:17:46 -04:00
Alessio Gravili
e6d4445a8a chore: fix build 2024-03-27 16:03:23 -04:00
Jacob Fletcher
0f0da809da Merge pull request #5497 from payloadcms/fix/table-cols
Fix/table cols
2024-03-27 16:00:06 -04:00
Alessio Gravili
db8e805a96 fix: improve error path merging from server, make sure no new or removed rows/values coming from the server are being considered outside addFieldRow 2024-03-27 15:55:19 -04:00
Jacob Fletcher
a882cc7e8e fix(ui): properly handles column selector labels 2024-03-27 15:45:26 -04:00
Elliot DeNolf
acede26aa6 fix: imports from exports dir (#5496)
* chore: remove all internal importing from exports directory

* chore: more package.json main values to src/index.ts
2024-03-27 15:39:58 -04:00
Paul
9330919be8 fix: issue of losing locale when switching tabs between API and edit views (#5494)
* fix: issue of losing locale when switching tabs between API and edit views
2024-03-27 16:20:04 -03:00
Jacob Fletcher
91b4e91e9c fix(ui): filters fields and unfurls subfields for table columns 2024-03-27 13:00:54 -04:00
Alessio Gravili
08ff286f9a chore: e2e: replace flaky manual save actions with non-flaky saveDocAndAssert helper 2024-03-27 12:18:17 -04:00
Jacob Fletcher
bc9fe2f4f9 fix(ui): disables bulk edit for default id fields 2024-03-27 12:13:29 -04:00
Alessio Gravili
e99b168bd6 ci: do not retry failing tests - we shouldn't have flaky tests in the first place 2024-03-27 12:03:38 -04:00
Alessio Gravili
a7afb1f680 chore: enable all lexical e2e tests 2024-03-27 11:54:12 -04:00
Alessio Gravili
6f3934f2e5 fix: server form-state request wasn't triggered on first onChange 2024-03-27 11:44:50 -04:00
Alessio Gravili
79da297add fix: form state not replaced if server has different data for rows 2024-03-27 11:44:02 -04:00
Alessio Gravili
6664535ab9 fix: form onChange using out-of-date old fields state 2024-03-27 11:43:35 -04:00
Alessio Gravili
80d290e178 fix: server-generated ID for base ID field is overriden on the client 2024-03-27 11:42:08 -04:00
Alessio Gravili
d144af6d8e fix: baseIDField not included in form states 2024-03-27 11:41:21 -04:00
1270 changed files with 38855 additions and 39681 deletions

38
.github/CODEOWNERS vendored
View File

@@ -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

View File

@@ -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
View 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
View File

@@ -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
View 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>

View File

@@ -1 +1 @@
v18.19.1
v18.20.2

2
.nvmrc
View File

@@ -1 +1 @@
v18.19.1
v18.20.2

View File

@@ -10,3 +10,5 @@
**/temp
**/docs/**
tsconfig.json
packages/payload/*.js
packages/payload/*.d.ts

54
.vscode/launch.json vendored
View File

@@ -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}",

View File

@@ -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

View File

@@ -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: {

View File

@@ -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)

View File

@@ -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)

View File

@@ -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)

View File

@@ -0,0 +1,8 @@
#custom-css {
font-family: monospace;
background-image: url('/placeholder.png');
}
#custom-css::after {
content: 'custom-css';
}

View File

@@ -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'

View File

@@ -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
}

View File

@@ -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
}

View File

@@ -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
}

View File

@@ -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
}

View File

@@ -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>

View File

@@ -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. |

View File

@@ -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._

View File

@@ -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

View File

@@ -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._

View File

@@ -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

View File

@@ -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
```

View File

@@ -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._

View File

@@ -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._

View File

@@ -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._

View File

@@ -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:**

View File

@@ -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'

View File

@@ -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

View File

@@ -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

View File

@@ -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",

View File

@@ -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:

View File

@@ -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
],
},
},
},
},
},
}),
},
],
}

View File

@@ -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._

View File

@@ -1 +0,0 @@
export default {}

View File

@@ -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

View File

@@ -10,6 +10,12 @@ const withBundleAnalyzer = bundleAnalyzer({
export default withBundleAnalyzer(
withPayload({
reactStrictMode: false,
eslint: {
ignoreDuringBuilds: true,
},
typescript: {
ignoreBuildErrors: true,
},
async redirects() {
return [
{

View File

@@ -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"
}
}

View File

@@ -10,6 +10,6 @@
}
},
"module": {
"type": "commonjs"
"type": "es6"
}
}

View File

@@ -1,2 +1,4 @@
#!/usr/bin/env node
require('../dist/index.js')
import { main } from '../dist/index.js'
main()

View File

@@ -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

View File

@@ -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": {

View File

@@ -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}`))

View File

@@ -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 : ''}`,
)
}
}

View File

@@ -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
})
})

View File

@@ -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 : ''}`)
}
}

View File

@@ -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'
}

View File

@@ -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 || '',",
' },',
' }),',
],

View File

@@ -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)
}

View File

@@ -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
}

View File

@@ -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 {

View File

@@ -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',
// },
]
}

View File

@@ -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)')
})
})
})

View File

@@ -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 = {

View File

@@ -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) {

View File

@@ -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')
}
}
}

View File

@@ -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 })
}

View File

@@ -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) => {

View File

@@ -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)
}

View File

@@ -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, {

View File

@@ -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"]

View File

@@ -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"

View 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,
}
}

View File

@@ -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

View File

@@ -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,

View File

@@ -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)

View File

@@ -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)

View File

@@ -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
}
},
)

View File

@@ -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',

View File

@@ -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, '\\$&'),
}
}
}

View File

@@ -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]
}

View 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
}

View File

@@ -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",

View 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) }
}

View File

@@ -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

View File

@@ -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

View File

@@ -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,

View File

@@ -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, '')

View File

@@ -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`

View File

@@ -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,

View File

@@ -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({

View File

@@ -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({

View File

@@ -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
}

View File

@@ -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,
})
}

View File

@@ -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 }) => {

View File

@@ -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({

View File

@@ -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],

View File

@@ -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({

View File

@@ -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

View File

@@ -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({

View File

@@ -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,
}
}

View File

@@ -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,
})
}
})

View File

@@ -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 {

View File

@@ -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,
}
}
}

View File

@@ -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