Compare commits

..

480 Commits

Author SHA1 Message Date
Elliot DeNolf
e9c7f9da92 chore(release): v3.0.0-alpha.61 [skip ci] 2024-04-09 14:22:12 -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
Dan Ribbens
2fb265ae2d chore: fix broken fields test (#5492) 2024-03-27 12:39:09 -04:00
Elliot DeNolf
3b5e7f1dc4 chore: unprettify tsconfig.json to avoid needing to reformat 2024-03-27 12:28:19 -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
Paul
aba7c13a1d chore: rename the DB strings in buildconfigwithdefaults to DATABASE_URI (#5490)
* chore: rename the DB strings in buildconfigwithdefaults to DATABASE_URI

* reverse name change for postgres strings
2024-03-27 12:34:10 -03:00
Paul
f59d3f36d1 chore: add process.env.MONGO_URL to buildconfigwithdefaults with fallback to string (#5489) 2024-03-27 12:00:42 -03:00
Jacob Fletcher
3f0d0ecd5f fix(ui): prevents field errors from flashing when used outside of field props context (#5488) 2024-03-27 10:48:05 -04:00
Elliot DeNolf
0c51502cc5 Merge pull request #5464 from payloadcms/feat/cpa-updates
feat(create-payload-app): updates
2024-03-27 10:26:18 -04:00
Elliot DeNolf
e829650cd9 chore(create-payload-app): ESM test fix 2024-03-27 09:08:09 -04:00
Jacob Fletcher
a8082c551b fix(next): removes reliance on instanceof from api error formatting (#5482) 2024-03-27 09:06:47 -04:00
Elliot DeNolf
ff55cfa001 ci: conditionally set jest reporter 2024-03-26 18:45:53 -04:00
Elliot DeNolf
623a3d3b7b ci: re-enable cpa test suite 2024-03-26 18:45:53 -04:00
Elliot DeNolf
fb32e2a561 test(create-payload-app): only use local-template in tests 2024-03-26 18:45:53 -04:00
Elliot DeNolf
8aa8a380e1 test: missing test dir deps, update cpa create project suite 2024-03-26 18:45:53 -04:00
Elliot DeNolf
f35b8b05e8 chore(create-payload-app): remove unneeded 2.0 func, add cli overrides 2024-03-26 18:45:53 -04:00
Elliot DeNolf
05bb73bb7c chore: update blank 3.0 template 2024-03-26 18:45:53 -04:00
Elliot DeNolf
18299dc65e chore: blank 3.0 template type 2024-03-26 18:45:53 -04:00
Elliot DeNolf
818ab2c10f chore: restructure 2024-03-26 18:45:52 -04:00
Elliot DeNolf
df0bf28d57 chore: return error messages from parse 2024-03-26 18:45:52 -04:00
Elliot DeNolf
ff65f10c2f chore: edge case for parsing next config 2024-03-26 18:45:52 -04:00
Elliot DeNolf
5cf49aa166 chore: implement AST parsing of next config 2024-03-26 18:45:52 -04:00
Elliot DeNolf
0651daa1d4 chore: more ESM, linting 2024-03-26 18:45:52 -04:00
Elliot DeNolf
921c53f75c chore(templates): bump alpha deps and next app changes 2024-03-26 18:45:52 -04:00
Elliot DeNolf
dd37519185 chore: missing semis on payload pointer files 2024-03-26 18:45:09 -04:00
Dan Ribbens
a1e8c4eb2b fix(db-postgres): query with contains operator hasMany (#5481) 2024-03-26 15:26:46 -04:00
Paul
1f8c191cb3 fix: missing data in livepreview causing missing updated at values (#5477) 2024-03-26 15:15:14 -03:00
Paul
f4acc74eee chore: added iframe content test for live-preview test (#5476) 2024-03-26 15:03:18 -03:00
Dan Ribbens
3d1378ab77 fix: duplicate db called with incorrect data (#5475) 2024-03-26 13:51:25 -04:00
Dan Ribbens
58e4174edb fix(db-postgres): deleteOne handle joins (#5457)
* fix(db-postgres): deleteOne handle joins

* chore(db-postgres): reduce duplicate lines of code

* chore: optimize delete preferences

* chore(db-postgres): fix deleteOne regression

* chore(db-postgres): missing await
2024-03-26 13:36:15 -04:00
Jacob Fletcher
20b4585666 chore: properly types cell components (#5474) 2024-03-26 12:08:33 -04:00
Paul
9c7e7ed8d4 fix(ui): custom buttons and e2e refresh permissions test (#5458)
* moved refresh permissions test suite to access control

* support for custom Save, SaveDraft and Publish buttons in admin config for collections and globals

* moved navigation content to client side so that permissions can be refreshed from active state
2024-03-26 11:48:00 -03:00
Alessio Gravili
436c4f2736 fix: missing withConditions for Upload, Select, Password, Blocks, Array fields (#5471)
* fix: missing withConditions for Upload, Select, Password, Blocks, Array fields. Fixes Lexical e2e tests

* chore: skip failing lexical test for now
2024-03-26 10:38:48 -04:00
Dan Ribbens
0ce752af79 fix: regression of filterOptions using different transaction (#5450) 2024-03-26 10:13:57 -04:00
Jarrod Flesch
92ff896bdb chore: adjusts e2e to watch for new file url paths (#5467) 2024-03-26 00:44:21 -04:00
Alessio Gravili
77a3cbaba5 Merge pull request #5466 from payloadcms/temp20
chore: improve e2e and int test speed, reduce flakiness and errors
2024-03-26 00:13:50 -04:00
Alessio Gravili
56f9c88251 chore: optimize test seed payload.create calls and run them in parallel to reduce MongoDB errors 2024-03-25 23:58:55 -04:00
Alessio Gravili
8a5a08cbe1 chore: fix test seed helper keeping all uploads deleted during test run, as they weren't restored like the db snapshot 2024-03-25 23:57:54 -04:00
Alessio Gravili
5241c38ba0 chore: speed up tests by not running seed twice for the first test, and reduce flakiness of lexical e2e test suite 2024-03-25 23:55:42 -04:00
Jacob Fletcher
072a903351 Merge pull request #5461 from payloadcms/fix/misc-views
Fix/misc views
2024-03-25 23:35:15 -04:00
Jacob Fletcher
328bd453bb chore(i18n): adds version:status to client translations 2024-03-25 22:56:28 -04:00
Jacob Fletcher
690a3cfa68 fix(ui): threads data through document info context 2024-03-25 22:56:28 -04:00
Jacob Fletcher
1c1847f63c fix(next): dynamic params for custom collection and global views 2024-03-25 22:56:19 -04:00
Alessio Gravili
c3d9d8ee2f Merge pull request #5460 from payloadcms/temp14
fix: various issues impacting lexical e2e tests
2024-03-25 20:50:57 -04:00
Alessio Gravili
65932b65d2 chore: fields test: fix Mongo write errors during seed by making create calls run sequentially.
Adds easy way of toggling between parallel or sequential runs, and optimized performance of create calls
2024-03-25 20:39:56 -04:00
Alessio Gravili
682e961416 chore: run lexical e2e's in CI, adjust runE2E to allow running just the fields/lexical.e2e.spec.ts 2024-03-25 17:17:13 -04:00
Alessio Gravili
9fcccc8197 chore: add payload/no-jsx-import-statements eslint rule to eslint-config-payload by default 2024-03-25 17:08:43 -04:00
Alessio Gravili
72f3ced219 chore: fix incorrect logic in auth test 2024-03-25 17:07:55 -04:00
Alessio Gravili
74de066529 chore: skip last lexical e2e test for now 2024-03-25 16:56:44 -04:00
Alessio Gravili
3d1589404c fix: race condition between form modified setter and form onSubmit. Caused e2e flakes 2024-03-25 16:53:07 -04:00
Patrik
30d9d46dd8 test: passing point fields test suite (#5401)
* test: passing point fields test suite

* chore: removes waits from point fields test suite

* chore: removes unnecessary waits in dates field test suite

* chore: removes waits entirely from dates tests

* chore: adds translates function for longitude/latitude

* chore: renames coordinate function and conditionally renders hypen in the function
2024-03-25 16:36:02 -04:00
Alessio Gravili
740373897a fix(richtext-lexical): Blocks: field schemas for sub-fields were not handled 2024-03-25 15:42:15 -04:00
Elliot DeNolf
f7ca01bafd test: convert PrePopulateFieldUI to client component (#5456) 2024-03-25 15:38:37 -04:00
Jacob Fletcher
7654ff686a fix(ui): throws explicit error for custom view tabs that are client components 2024-03-25 15:37:57 -04:00
Jarrod Flesch
5266612bb3 chore: get correct labels for unnamed fields in list view (#5454) 2024-03-25 15:31:23 -04:00
Patrik
a9b46a4d63 fix(ui): properly formats collapsible field IDs (#5435)
* test: passing collapsible fields test suite

* chore: passes indexPath into ArrayRow & updates path in collapsible field

* fix: collapsible paths and indexPath prop types

* chore: improves path and schemaPath syntax

* leftover

* chore: updates selectors in collapsibles tests

* chore: updates selector in live-preview test suite

---------

Co-authored-by: Jacob Fletcher <jacobsfletch@gmail.com>
2024-03-25 14:16:35 -04:00
Elliot DeNolf
76e9bd8ad6 chore: enable no-jsx-import-statements for test dir 2024-03-25 13:51:30 -04:00
Elliot DeNolf
317a443644 chore: remove unused update changelog script 2024-03-25 13:40:07 -04:00
Elliot DeNolf
43f91ca42c test: run prettier on tsconfig.json after test 2024-03-25 13:36:29 -04:00
Alessio Gravili
8d78d07415 fix: prioritize props path in useField - fixes sub-fields initialized from within fields, like blockName (#5451) 2024-03-25 13:21:01 -04:00
Jarrod Flesch
99a00a1ae2 fix(tests): number field e2e (#5452) 2024-03-25 13:17:13 -04:00
Patrik
2cd8d891a1 test: passing date fields test suite (#5412)
* test: passing date fields test suite

* chore: reverts dates from 2024 back to 2023 to remove clutter
2024-03-25 11:49:44 -04:00
Dan Ribbens
7fc33af1e5 fix: image resize tiff files (#5449) 2024-03-25 11:09:17 -04:00
Alessio Gravili
67c57a1137 Merge pull request #5436 from payloadcms/temp11
chore: improvements to eslint, and access-control + lexical test suites
2024-03-25 10:48:27 -04:00
Alessio Gravili
9f8ac06659 fix: already-sanitized fields were sanitized twice in buildFieldSchemaMap 2024-03-25 10:36:52 -04:00
Alessio Gravili
aea28b28d0 Merge remote-tracking branch 'origin/alpha' into temp11 2024-03-25 10:18:08 -04:00
Paul
ee4cd61696 chore: enable e2e live preview (#5444) 2024-03-25 08:39:34 -03:00
Elliot DeNolf
e9f15c377f chore: lint mdx in lint-staged 2024-03-24 23:18:52 -04:00
Elliot DeNolf
d5935ea81b chore: lint all json/yml, add to lint-staged 2024-03-24 23:16:26 -04:00
Alessio Gravili
934ad96a98 chore: unflake lexical 2024-03-22 17:09:06 -04:00
Alessio Gravili
2c68f8fba1 chore: unflake access-control, fix incorrect poll & toPass timeouts 2024-03-22 16:42:41 -04:00
Elliot DeNolf
c90de87f37 ci: release script updates 2024-03-22 16:32:23 -04:00
Elliot DeNolf
ab84566d86 ci(deps): update turborepo 2024-03-22 16:19:29 -04:00
Alessio Gravili
4c109a467f chore: AdminUrlUtil: add ?limit=10 to list view url generator, as it would automatically redirect anyways. Had potential for flaky tests 2024-03-22 15:59:03 -04:00
Alessio Gravili
bc4f6aaf9c chore: improve id type of adminUrlUtil 2024-03-22 15:46:01 -04:00
Alessio Gravili
cf8ac7e8b3 chore: fix no-flaky-assertions not working withing .toPass callbacks 2024-03-22 15:41:10 -04:00
Alessio Gravili
3a9b230aef chore: fix payload/no-flaky-assertions not working for chained assertions (e.g. .not.toBe() instead of just .toBe()) 2024-03-22 15:33:02 -04:00
Alessio Gravili
016b644d86 fix: do not error if row field has no fields (#5433) 2024-03-22 14:29:13 -04:00
881 changed files with 28981 additions and 35344 deletions

View File

@@ -2,10 +2,14 @@ name: build
on:
pull_request:
types: [ opened, reopened, synchronize ]
types: [opened, reopened, synchronize]
push:
branches: ['main', 'alpha']
concurrency:
group: ${{ github.workflow }}-${{ github.ref }}
cancel-in-progress: true
jobs:
changes:
runs-on: ubuntu-latest
@@ -35,7 +39,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
@@ -61,34 +65,32 @@ 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
if: false # Disable until tests are updated for 3.0
steps:
- uses: actions/checkout@v4
with:
fetch-depth: 25
- name: Use Node.js 18
uses: actions/setup-node@v4
with:
@@ -100,32 +102,27 @@ jobs:
version: 8
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:
database:
- mongodb
# - postgres
- mongodb
- postgres
# - postgres-custom-schema
# - postgres-uuid
# - supabase
@@ -152,6 +149,7 @@ jobs:
- name: Restore build
uses: actions/cache@v4
timeout-minutes: 10
with:
path: ./*
key: ${{ github.sha }}-${{ github.run_number }}
@@ -162,7 +160,7 @@ jobs:
- name: Start PostgreSQL
uses: CasperWA/postgresql-action@v1.2
with:
postgresql version: '14' # See https://hub.docker.com/_/postgres for available versions
postgresql version: '14' # See https://hub.docker.com/_/postgres for available versions
postgresql db: ${{ env.POSTGRES_DB }}
postgresql user: ${{ env.POSTGRES_USER }}
postgresql password: ${{ env.POSTGRES_PASSWORD }}
@@ -202,7 +200,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 +208,7 @@ jobs:
tests-e2e:
runs-on: ubuntu-latest
needs: core-build
needs: build
strategy:
fail-fast: false
matrix:
@@ -218,18 +216,23 @@ jobs:
suite:
- _community
- access-control
# - admin
- admin
- auth
# - field-error-states
# - fields-relationship
# - fields
# - live-preview
# - localization
# - plugin-nested-docs
# - plugin-seo
# - refresh-permissions
# - uploads
# - versions
- email
- field-error-states
- fields-relationship
- fields
- fields__collections__Blocks
- fields__collections__Array
- fields__collections__Relationship
- fields__collections__Lexical
- live-preview
- localization
- plugin-form-builder
- plugin-nested-docs
- plugin-seo
- versions
- uploads
steps:
- name: Use Node.js 18
@@ -245,32 +248,28 @@ jobs:
- name: Restore build
uses: actions/cache@v4
timeout-minutes: 10
with:
path: ./*
key: ${{ github.sha }}-${{ github.run_number }}
- 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/
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
@@ -286,6 +285,7 @@ jobs:
- name: Restore build
uses: actions/cache@v4
timeout-minutes: 10
with:
path: ./*
key: ${{ github.sha }}-${{ github.run_number }}
@@ -296,47 +296,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
if: matrix.pkg != 'create-payload-app' # degit doesn't work within GitHub Actions
templates:
needs: changes
if: false # Disable until templates are updated for 3.0
@@ -344,7 +303,7 @@ jobs:
strategy:
fail-fast: false
matrix:
template: [ blank, website, ecommerce ]
template: [blank, website, ecommerce]
steps:
- uses: actions/checkout@v4

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

@@ -9,4 +9,6 @@
**/node_modules
**/temp
**/docs/**
./tsconfig.json
tsconfig.json
packages/payload/*.js
packages/payload/*.d.ts

31
.vscode/launch.json vendored
View File

@@ -10,19 +10,37 @@
"cwd": "${workspaceFolder}"
},
{
"command": "pnpm run dev _community -- --no-turbo",
"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": "pnpm run dev plugin-cloud-storage",
"cwd": "${workspaceFolder}",
@@ -34,18 +52,21 @@
}
},
{
"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",

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,7 +1,7 @@
/* 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_PATCH, REST_POST } from '@payloadcms/next/routes'
export const GET = REST_GET(config)
export const POST = REST_POST(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

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

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

@@ -173,7 +173,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 +207,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:

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

@@ -32,4 +32,4 @@
"ts-node": "^9.1.1",
"typescript": "^4.8.4"
}
}
}

View File

@@ -1,11 +1,7 @@
{
"compilerOptions": {
"target": "es5",
"lib": [
"dom",
"dom.iterable",
"esnext"
],
"lib": ["dom", "dom.iterable", "esnext"],
"allowJs": true,
"strict": false,
"esModuleInterop": true,
@@ -16,22 +12,12 @@
"sourceMap": true,
"resolveJsonModule": true,
"paths": {
"payload/generated-types": [
"./src/payload-types.ts"
],
"node_modules/*": [
"./node_modules/*"
]
},
"payload/generated-types": ["./src/payload-types.ts"],
"node_modules/*": ["./node_modules/*"]
}
},
"include": [
"src"
],
"exclude": [
"node_modules",
"dist",
"build",
],
"include": ["src"],
"exclude": ["node_modules", "dist", "build"],
"ts-node": {
"transpileOnly": true
}

View File

@@ -1,11 +1,7 @@
{
"compilerOptions": {
"target": "es5",
"lib": [
"dom",
"dom.iterable",
"esnext"
],
"lib": ["dom", "dom.iterable", "esnext"],
"outDir": "./dist",
"skipLibCheck": true,
"strict": false,
@@ -18,10 +14,8 @@
"jsx": "preserve",
"sourceMap": true
},
"include": [
"src"
],
"include": ["src"],
"ts-node": {
"transpileOnly": true
}
}
}

View File

@@ -26,4 +26,4 @@
"ts-node": "^9.1.1",
"typescript": "^4.8.4"
}
}
}

View File

@@ -1,11 +1,7 @@
{
"compilerOptions": {
"target": "es5",
"lib": [
"dom",
"dom.iterable",
"esnext"
],
"lib": ["dom", "dom.iterable", "esnext"],
"allowJs": true,
"strict": false,
"esModuleInterop": true,
@@ -14,14 +10,8 @@
"rootDir": "./src",
"jsx": "react"
},
"include": [
"src",
],
"exclude": [
"node_modules",
"dist",
"build",
],
"include": ["src"],
"exclude": ["node_modules", "dist", "build"],
"ts-node": {
"transpileOnly": true
}

View File

@@ -26,4 +26,4 @@
"ts-node": "^9.1.1",
"typescript": "^4.8.4"
}
}
}

View File

@@ -1,11 +1,7 @@
{
"compilerOptions": {
"target": "es5",
"lib": [
"dom",
"dom.iterable",
"esnext"
],
"lib": ["dom", "dom.iterable", "esnext"],
"allowJs": true,
"strict": false,
"esModuleInterop": true,
@@ -14,14 +10,8 @@
"rootDir": "./src",
"jsx": "react"
},
"include": [
"src"
],
"exclude": [
"node_modules",
"dist",
"build",
],
"include": ["src"],
"exclude": ["node_modules", "dist", "build"],
"ts-node": {
"transpileOnly": true
}

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'],
@@ -18,4 +17,8 @@ const customJestConfig = {
verbose: true,
}
export default customJestConfig
if (process.env.CI) {
baseJestConfig.reporters = [['github-actions', { silent: false }], 'summary']
}
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-alpha.61",
"private": true,
"type": "module",
"workspaces:": [
@@ -46,10 +46,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 +57,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 +65,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 +76,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 +88,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",
@@ -109,8 +109,8 @@
"dotenv": "8.6.0",
"drizzle-kit": "0.20.14-1f2c838",
"drizzle-orm": "0.29.4",
"eslint-plugin-payload": "workspace:*",
"escape-html": "^1.0.3",
"eslint-plugin-payload": "workspace:*",
"execa": "5.1.1",
"form-data": "3.0.1",
"fs-extra": "10.1.0",
@@ -131,10 +131,11 @@
"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 +146,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,20 +154,20 @@
"tempy": "^1.0.1",
"ts-node": "10.9.1",
"tsx": "^4.7.1",
"turbo": "^1.12.5",
"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"
},
"lint-staged": {
"*.{md,mdx,yml,json}": "prettier --write",
"*.{js,jsx,ts,tsx}": [
"prettier --write",
"eslint --cache --fix"
@@ -194,8 +195,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,18 +1,25 @@
{
"name": "create-payload-app",
"version": "1.0.0",
"version": "3.0.0-alpha.61",
"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 build:swc",
"copyfiles": "copyfiles -u 2 \"../../app/(payload)/**\" \"dist\"",
"build": "pnpm pack-template-files && pnpm typecheck && pnpm build:swc",
"typecheck": "tsc",
"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",
@@ -20,6 +27,7 @@
"bin"
],
"dependencies": {
"@clack/prompts": "^0.7.0",
"@sindresorhus/slugify": "^1.1.0",
"arg": "^5.0.0",
"chalk": "^4.1.0",
@@ -27,21 +35,26 @@
"comment-json": "^4.2.3",
"degit": "^2.8.4",
"detect-package-manager": "^3.0.1",
"esprima": "^4.0.1",
"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": {
"@types/command-exists": "^1.2.0",
"@types/degit": "^2.8.3",
"@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": {
"import": "./src/lib/init-next.ts",
"require": "./src/lib/init-next.ts",
"types": "./src/lib/init-next.ts"
}
}
}

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,72 +1,42 @@
import fse from 'fs-extra'
import path from 'path'
import globby from 'globby'
import type { DbDetails } from '../types.js'
import { warning } from '../utils/log.js'
import { bundlerPackages, dbPackages, editorPackages } from './packages.js'
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 = path.resolve(args.projectDir, 'package.json')
try {
const packageObj = await fse.readJson(packageJsonPath)
packageObj.dependencies['payload'] = '^2.0.0'
const dbPackage = dbPackages[args.dbDetails.type]
const bundlerPackage = bundlerPackages['webpack']
const editorPackage = editorPackages['slate']
// Delete all other db adapters
Object.values(dbPackages).forEach((p) => {
if (p.packageName !== dbPackage.packageName) {
delete packageObj.dependencies[p.packageName]
}
})
packageObj.dependencies[dbPackage.packageName] = dbPackage.version
packageObj.dependencies[bundlerPackage.packageName] = bundlerPackage.version
packageObj.dependencies[editorPackage.packageName] = editorPackage.version
await fse.writeJson(packageJsonPath, packageObj, { spaces: 2 })
} catch (err: unknown) {
warning('Unable to update name in package.json')
}
try {
const possiblePaths = [
path.resolve(args.projectDir, 'src/payload.config.ts'),
path.resolve(args.projectDir, 'src/payload/payload.config.ts'),
]
let payloadConfigPath: string | undefined
possiblePaths.forEach((p) => {
if (fse.pathExistsSync(p) && !payloadConfigPath) {
payloadConfigPath = p
}
})
if (!('payloadConfigPath' 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
}
const configContent = fse.readFileSync(payloadConfigPath, 'utf-8')
const configLines = configContent.split('\n')
const dbReplacement = dbPackages[args.dbDetails.type]
const bundlerReplacement = bundlerPackages['webpack']
const editorReplacement = editorPackages['slate']
const dbReplacement = dbReplacements[args.dbDetails.type]
let dbConfigStartLineIndex: number | undefined
let dbConfigEndLineIndex: number | undefined
@@ -75,21 +45,6 @@ export async function configurePayloadConfig(args: {
if (l.includes('// database-adapter-import')) {
configLines[i] = dbReplacement.importReplacement
}
if (l.includes('// bundler-import')) {
configLines[i] = bundlerReplacement.importReplacement
}
if (l.includes('// bundler-config')) {
configLines[i] = bundlerReplacement.configReplacement
}
if (l.includes('// editor-import')) {
configLines[i] = editorReplacement.importReplacement
}
if (l.includes('// editor-config')) {
configLines[i] = editorReplacement.configReplacement
}
if (l.includes('// database-adapter-config-start')) {
dbConfigStartLineIndex = i
@@ -112,6 +67,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

@@ -1,12 +1,16 @@
import fse from 'fs-extra'
import path from 'path'
import type { BundlerType, CliArgs, DbType, ProjectTemplate } from '../types.js'
import type { CliArgs, DbType, ProjectTemplate } from '../types.js'
import { createProject } from './create-project.js'
import { bundlerPackages, dbPackages, editorPackages } from './packages.js'
import exp from 'constants'
import { fileURLToPath } from 'node:url'
import { dbReplacements } from './packages.js'
import { getValidTemplates } from './templates.js'
import globby from 'globby'
const projectDir = path.resolve(__dirname, './tmp')
const filename = fileURLToPath(import.meta.url)
const dirname = path.dirname(filename)
const projectDir = path.resolve(dirname, './tmp')
describe('createProject', () => {
beforeAll(() => {
console.log = jest.fn()
@@ -28,33 +32,11 @@ describe('createProject', () => {
const args = {
_: ['project-name'],
'--db': 'mongodb',
'--local-template': 'blank',
'--no-deps': true,
} as CliArgs
const packageManager = 'yarn'
it('creates starter project', async () => {
const projectName = 'starter-project'
const template: ProjectTemplate = {
name: 'blank',
type: 'starter',
url: 'https://github.com/payloadcms/payload/templates/blank',
description: 'Blank Template',
}
await createProject({
cliArgs: args,
projectName,
projectDir,
template,
packageManager,
})
const packageJsonPath = path.resolve(projectDir, 'package.json')
const packageJson = fse.readJsonSync(packageJsonPath)
// Check package name and description
expect(packageJson.name).toEqual(projectName)
})
it('creates plugin template', async () => {
const projectName = 'plugin'
const template: ProjectTemplate = {
@@ -78,26 +60,34 @@ describe('createProject', () => {
expect(packageJson.name).toEqual(projectName)
})
describe('db adapters and bundlers', () => {
describe('creates project from template', () => {
const templates = getValidTemplates()
it.each([
['blank', 'mongodb', 'webpack'],
['blank', 'postgres', 'webpack'],
['website', 'mongodb', 'webpack'],
['website', 'postgres', 'webpack'],
['ecommerce', 'mongodb', 'webpack'],
['ecommerce', 'postgres', 'webpack'],
])('update config and deps: %s, %s, %s', async (templateName, db, bundler) => {
['blank-3.0', 'mongodb'],
['blank-3.0', 'postgres'],
// TODO: Re-enable these once 3.0 is stable and templates updated
// ['website', 'mongodb'],
// ['website', 'postgres'],
// ['ecommerce', 'mongodb'],
// ['ecommerce', 'postgres'],
])('update config and deps: %s, %s', async (templateName, db) => {
const projectName = 'starter-project'
const template = templates.find((t) => t.name === templateName)
const cliArgs = {
...args,
'--db': db,
'--local-template': templateName,
} as CliArgs
await createProject({
cliArgs: args,
cliArgs,
projectName,
projectDir,
template,
template: template as ProjectTemplate,
packageManager,
dbDetails: {
dbUri: `${db}://localhost:27017/create-project-test`,
@@ -105,35 +95,27 @@ describe('createProject', () => {
},
})
const dbReplacement = dbPackages[db as DbType]
const bundlerReplacement = bundlerPackages[bundler as BundlerType]
const editorReplacement = editorPackages['slate']
const dbReplacement = dbReplacements[db as DbType]
const packageJsonPath = path.resolve(projectDir, 'package.json')
const packageJson = fse.readJsonSync(packageJsonPath)
// Check deps
expect(packageJson.dependencies['payload']).toEqual('^2.0.0')
expect(packageJson.dependencies[dbReplacement.packageName]).toEqual(dbReplacement.version)
// Should only have one db adapter
expect(
Object.keys(packageJson.dependencies).filter((n) => n.startsWith('@payloadcms/db-')),
).toHaveLength(1)
expect(packageJson.dependencies[bundlerReplacement.packageName]).toEqual(
bundlerReplacement.version,
)
expect(packageJson.dependencies[editorReplacement.packageName]).toEqual(
editorReplacement.version,
)
const payloadConfigPath = (
await globby('**/payload.config.ts', {
absolute: true,
cwd: projectDir,
})
)?.[0]
let payloadConfigPath = path.resolve(projectDir, 'src/payload.config.ts')
// 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
@@ -143,18 +125,7 @@ describe('createProject', () => {
expect(content).not.toContain('// database-adapter-config-start')
expect(content).not.toContain('// database-adapter-config-end')
expect(content).toContain(dbReplacement.configReplacement.join('\n'))
expect(content).not.toContain('// bundler-config-import')
expect(content).toContain(bundlerReplacement.importReplacement)
expect(content).not.toContain('// bundler-config')
expect(content).toContain(bundlerReplacement.configReplacement)
})
})
})
describe('Templates', () => {
it.todo('Verify that all templates are valid')
// Loop through all templates.ts that should have replacement comments, and verify that they are present
})
})

View File

@@ -1,15 +1,19 @@
import * as p from '@clack/prompts'
import chalk from 'chalk'
import degit from 'degit'
import execa from 'execa'
import fse from 'fs-extra'
import ora from 'ora'
import { fileURLToPath } from 'node:url'
import path from 'path'
import type { CliArgs, DbDetails, PackageManager, ProjectTemplate } from '../types.js'
import { 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)
const dirname = path.dirname(filename)
async function createOrFindProjectDir(projectDir: string): Promise<void> {
const pathExists = await fse.pathExists(projectDir)
if (!pathExists) {
@@ -40,7 +44,7 @@ async function installDeps(args: {
})
return true
} catch (err: unknown) {
console.log({ err })
error(`Error installing dependencies${err instanceof Error ? `: ${err.message}` : ''}.`)
return false
}
}
@@ -55,19 +59,37 @@ export async function createProject(args: {
}): Promise<void> {
const { cliArgs, dbDetails, packageManager, projectDir, projectName, template } = args
if (cliArgs['--dry-run']) {
debug(`Dry run: Creating project in ${chalk.green(projectDir)}`)
return
}
await createOrFindProjectDir(projectDir)
console.log(`\n Creating project in ${chalk.green(path.resolve(projectDir))}\n`)
if ('url' in template) {
const emitter = degit(template.url)
if (cliArgs['--local-template']) {
// Copy template from local path. For development purposes.
const localTemplate = path.resolve(
dirname,
'../../../../templates/',
cliArgs['--local-template'],
)
await fse.copy(localTemplate, projectDir)
} else if ('url' in template) {
let templateUrl = template.url
if (cliArgs['--template-branch']) {
templateUrl = `${template.url}#${cliArgs['--template-branch']}`
debug(`Using template url: ${templateUrl}`)
}
const emitter = degit(templateUrl)
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')
@@ -75,14 +97,16 @@ export async function createProject(args: {
await fse.remove(lockPath)
}
spinner.text = 'Installing dependencies...'
const result = await installDeps({ cliArgs, packageManager, projectDir })
spinner.stop()
spinner.clear()
if (result) {
success('Dependencies installed')
if (!cliArgs['--no-deps']) {
spinner.message('Installing dependencies...')
const result = await installDeps({ cliArgs, packageManager, projectDir })
if (result) {
spinner.stop('Successfully installed Payload and dependencies')
} else {
spinner.stop('Error installing dependencies', 1)
}
} else {
error('Error installing dependencies')
spinner.stop('Dependency installation skipped')
}
}
@@ -97,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,14 +1,13 @@
import type { CompilerOptions } from 'typescript'
import chalk from 'chalk'
import * as p from '@clack/prompts'
import { parse, stringify } from 'comment-json'
import { detect } from 'detect-package-manager'
import execa from 'execa'
import fs from 'fs'
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)
@@ -17,58 +16,85 @@ const dirname = path.dirname(filename)
import { fileURLToPath } from 'node:url'
import type { CliArgs } 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'> & {
projectDir?: string
dbType: DbType
nextAppDetails?: NextAppDetails
packageManager: PackageManager
projectDir: string
useDistFiles?: boolean
}
type InitNextResult = { reason?: string; success: boolean; userAppDir?: string }
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> {
args.projectDir = args.projectDir || process.cwd()
const { projectDir } = args
const templateResult = await applyPayloadTemplateFiles(args)
if (!templateResult.success) return templateResult
const { dbType: dbType, packageManager, projectDir } = args
const { success: installSuccess } = await installDeps(projectDir)
if (!installSuccess) {
return { ...templateResult, reason: 'Failed to install dependencies', success: false }
const nextAppDetails = args.nextAppDetails || (await getNextAppDetails(projectDir))
const { hasTopLevelLayout, isSrcDir, nextAppDir } =
nextAppDetails || (await getNextAppDetails(projectDir))
if (!nextAppDir) {
return { isSrcDir, reason: `Could not find app directory in ${projectDir}`, success: false }
}
// Create or find payload.config.ts
const createConfigResult = findOrCreatePayloadConfig(projectDir)
if (!createConfigResult.success) {
return { ...templateResult, ...createConfigResult }
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,
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')
const userTsConfigContent = await readFile(tsConfigPath, {
encoding: 'utf8',
@@ -80,51 +106,54 @@ async function addPayloadConfigToTsConfig(projectDir: string) {
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': [`./${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; useDistFiles?: boolean },
):
| { payloadConfigPath: string; success: true }
| { payloadConfigPath?: string; reason: string; success: false } {
const {
'--debug': debug,
nextAppDetails: { isSrcDir, nextAppDir, nextConfigPath } = {},
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', { 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 {
@@ -132,40 +161,40 @@ 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 userAppDirGlob = await globby(['**/app'], {
cwd: projectDir,
onlyDirectories: true,
})
const userAppDir = path.resolve(projectDir, userAppDirGlob?.[0])
if (!fs.existsSync(userAppDir)) {
return { reason: `Could not find user app directory inside ${projectDir}`, success: false }
} else {
logDebug(`Found user app directory: ${userAppDir}`)
}
logDebug(`Copying template files from ${templateFilesPath} to ${nextAppDir}`)
logDebug(`Copying template files from ${templateFilesPath} to ${userAppDir}`)
copyRecursiveSync(templateFilesPath, userAppDir, debug)
success('Successfully initialized.')
return { success: true, userAppDir }
const templateSrcDir = path.resolve(templateFilesPath, isSrcDir ? '' : 'src')
logDebug(`templateSrcDir: ${templateSrcDir}`)
logDebug(`nextAppDir: ${nextAppDir}`)
logDebug(`projectDir: ${projectDir}`)
logDebug(`nextConfigPath: ${nextConfigPath}`)
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 })
return {
payloadConfigPath: path.resolve(nextAppDir, '../payload.config.ts'),
success: true,
}
}
async function installDeps(projectDir: string) {
const packageManager = await detect({ cwd: projectDir })
if (!packageManager) {
throw new Error('Could not detect package manager')
}
async function installDeps(projectDir: string, packageManager: PackageManager, dbType: DbType) {
const packagesToInstall = ['payload', '@payloadcms/next', '@payloadcms/richtext-lexical'].map(
(pkg) => `${pkg}@alpha`,
)
info(`Installing dependencies with ${packageManager}`, 1)
const packagesToInstall = [
'payload',
'@payloadcms/db-mongodb',
'@payloadcms/next',
'@payloadcms/richtext-slate',
].map((pkg) => `${pkg}@alpha`)
packagesToInstall.push(`@payloadcms/db-${dbType}@alpha`)
let exitCode = 0
switch (packageManager) {
@@ -189,43 +218,45 @@ async function installDeps(projectDir: string) {
}
}
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 { slateEditor } from "@payloadcms/richtext-slate"; // 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
}
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,
onlyDirectories: true,
})
)?.[0]
if (!nextAppDir || nextAppDir.length === 0) {
nextAppDir = undefined
}
const hasTopLevelLayout = nextAppDir
? fs.existsSync(path.resolve(nextAppDir, 'layout.tsx'))
: false
return { hasTopLevelLayout, isSrcDir, nextAppDir, nextConfigPath }
}

View File

@@ -1,24 +1,9 @@
import type { BundlerType, DbType, EditorType } from '../types.js'
import type { DbType } from '../types.js'
type DbAdapterReplacement = {
configReplacement: string[]
importReplacement: string
packageName: string
version: string
}
type BundlerReplacement = {
configReplacement: string
importReplacement: string
packageName: string
version: string
}
type EditorReplacement = {
configReplacement: string
importReplacement: string
packageName: string
version: string
}
const mongodbReplacement: DbAdapterReplacement = {
@@ -26,7 +11,6 @@ const mongodbReplacement: DbAdapterReplacement = {
packageName: '@payloadcms/db-mongodb',
// Replacement between `// database-adapter-config-start` and `// database-adapter-config-end`
configReplacement: [' db: mongooseAdapter({', ' url: process.env.DATABASE_URI,', ' }),'],
version: '^1.0.0',
}
const postgresReplacement: DbAdapterReplacement = {
@@ -39,45 +23,9 @@ const postgresReplacement: DbAdapterReplacement = {
],
importReplacement: "import { postgresAdapter } from '@payloadcms/db-postgres'",
packageName: '@payloadcms/db-postgres',
version: '^0.x', // up to, not including 1.0.0
}
export const dbPackages: Record<DbType, DbAdapterReplacement> = {
export const dbReplacements: Record<DbType, DbAdapterReplacement> = {
mongodb: mongodbReplacement,
postgres: postgresReplacement,
}
const webpackReplacement: BundlerReplacement = {
importReplacement: "import { webpackBundler } from '@payloadcms/bundler-webpack'",
packageName: '@payloadcms/bundler-webpack',
// Replacement of line containing `// bundler-config`
configReplacement: ' bundler: webpackBundler(),',
version: '^1.0.0',
}
const viteReplacement: BundlerReplacement = {
configReplacement: ' bundler: viteBundler(),',
importReplacement: "import { viteBundler } from '@payloadcms/bundler-vite'",
packageName: '@payloadcms/bundler-vite',
version: '^0.x', // up to, not including 1.0.0
}
export const bundlerPackages: Record<BundlerType, BundlerReplacement> = {
vite: viteReplacement,
webpack: webpackReplacement,
}
export const editorPackages: Record<EditorType, EditorReplacement> = {
lexical: {
configReplacement: ' editor: lexicalEditor({}),',
importReplacement: "import { lexicalEditor } from '@payloadcms/richtext-lexical'",
packageName: '@payloadcms/richtext-lexical',
version: '^0.x', // up to, not including 1.0.0
},
slate: {
configReplacement: ' editor: slateEditor({}),',
importReplacement: "import { slateEditor } from '@payloadcms/richtext-slate'",
packageName: '@payloadcms/richtext-slate',
version: '^1.0.0',
},
}

View File

@@ -1,24 +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['--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'
@@ -23,7 +23,7 @@ const dbChoiceRecord: Record<DbType, DbChoice> = {
}
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,50 +34,39 @@ 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]
const dbUriRes = await prompts(
{
name: 'value',
type: 'text',
initial: `${dbChoice.dbConnectionPrefix}${
projectName === '.' ? `payload-${getRandomDigitSuffix()}` : slugify(projectName)
}`,
let dbUri: string | symbol | undefined = undefined
const initialDbUri = `${dbChoice.dbConnectionPrefix}${
projectName === '.' ? `payload-${getRandomDigitSuffix()}` : slugify(projectName)
}`
if (args['--db-accept-recommended']) {
dbUri = initialDbUri
} else if (args['--db-connection-string']) {
dbUri = args['--db-connection-string']
} else {
dbUri = await p.text({
initialValue: initialDbUri,
message: `Enter ${dbChoice.title.split(' ')[0]} connection string`, // strip beta from title
validate: (value: string) => !!value.length,
},
{
onCancel: () => {
process.exit(0)
},
},
)
})
if (p.isCancel(dbUri)) process.exit(0)
}
return {
type: dbChoice.value,
dbUri: dbUriRes.value,
dbUri,
}
}

View File

@@ -14,6 +14,12 @@ export function validateTemplate(templateName: string): boolean {
export function getValidTemplates(): ProjectTemplate[] {
return [
{
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',

View File

@@ -0,0 +1,61 @@
import { parseAndModifyConfigContent, withPayloadImportStatement } from './wrap-next-config.js'
import * as p from '@clack/prompts'
const 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
}
export default someFunc(
nextConfig
)
`
const nextConfigExportNamedDefault = `const nextConfig = {
// Your Next.js config here
}
const wrapped = someFunc(asdf)
export { wrapped as default }
`
describe('parseAndInsertWithPayload', () => {
it('should parse the default next config', () => {
const { modifiedConfigContent } = parseAndModifyConfigContent(defaultNextConfig)
expect(modifiedConfigContent).toContain(withPayloadImportStatement)
expect(modifiedConfigContent).toContain('withPayload(nextConfig)')
})
it('should parse the config with a function', () => {
const { modifiedConfigContent } = parseAndModifyConfigContent(nextConfigWithFunc)
expect(modifiedConfigContent).toContain('withPayload(someFunc(nextConfig))')
})
it('should parse the config with a function on a new line', () => {
const { modifiedConfigContent } = parseAndModifyConfigContent(nextConfigWithFuncMultiline)
expect(modifiedConfigContent).toContain(withPayloadImportStatement)
expect(modifiedConfigContent).toMatch(/withPayload\(someFunc\(\n nextConfig\n\)\)/)
})
// 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(
nextConfigExportNamedDefault,
)
expect(modifiedConfigContent).toContain(withPayloadImportStatement)
expect(success).toBe(false)
expect(warnLogSpy).toHaveBeenCalledWith(expect.stringContaining('Could not automatically wrap'))
})
})

View File

@@ -0,0 +1,146 @@
import chalk from 'chalk'
import { parseModule } from 'esprima'
import fs from 'fs'
import { warning } from '../utils/log.js'
import { log } from '../utils/log.js'
export const withPayloadImportStatement = `import { withPayload } from '@payloadcms/next'\n`
export const wrapNextConfig = (args: { nextConfigPath: string }) => {
const { nextConfigPath } = args
const configContent = fs.readFileSync(nextConfigPath, 'utf8')
const { modifiedConfigContent: newConfig, success } = parseAndModifyConfigContent(configContent)
if (!success) {
return
}
fs.writeFileSync(nextConfigPath, newConfig)
}
/**
* Parses config content with AST and wraps it with withPayload function
*/
export function parseAndModifyConfigContent(content: string): {
modifiedConfigContent: string
success: boolean
} {
content = withPayloadImportStatement + content
const ast = parseModule(content, { loc: true })
const exportDefaultDeclaration = ast.body.find((p) => p.type === 'ExportDefaultDeclaration') as
| Directive
| undefined
const exportNamedDeclaration = ast.body.find((p) => p.type === '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()
return {
modifiedConfigContent: content,
success: false,
}
}
}
warning('Could not automatically wrap next.config.js with withPayload.')
warnUserWrapNotSuccessful()
return {
modifiedConfigContent: content,
success: false,
}
}
function warnUserWrapNotSuccessful() {
// 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:`)}
import withPayload from '@payloadcms/next/withPayload'
const nextConfig = {
// Your Next.js config here
}
export default withPayload(nextConfig)
`
log(withPayloadMessage)
}
type Directive = {
declaration?: {
loc: Loc
}
}
type ExportNamedDeclaration = {
declaration: null
loc: Loc
specifiers: {
exported: {
loc: Loc
name: string
type: string
}
loc: Loc
local: {
loc: Loc
name: string
type: string
}
type: string
}[]
type: string
}
type Loc = {
end: { column: number; line: number }
start: { column: number; line: number }
}
function insertBeforeAndAfter(content: string, loc: Loc) {
const { end, start } = loc
const lines = content.split('\n')
const insert = (line: string, column: number, text: string) => {
return line.slice(0, column) + text + line.slice(column)
}
// insert ) after end
lines[end.line - 1] = insert(lines[end.line - 1], end.column, ')')
// insert withPayload before start
if (start.line === end.line) {
lines[end.line - 1] = insert(lines[end.line - 1], start.column, 'withPayload(')
} else {
lines[start.line - 1] = insert(lines[start.line - 1], start.column, 'withPayload(')
}
return lines.join('\n')
}

View File

@@ -1,20 +1,27 @@
import fs from 'fs-extra'
import path from 'path'
import type { ProjectTemplate } from '../types.js'
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: {
cliArgs: CliArgs
databaseUri: string
payloadSecret: string
projectDir: string
template: ProjectTemplate
template?: ProjectTemplate
}): Promise<void> {
const { databaseUri, payloadSecret, projectDir, template } = args
const { cliArgs, databaseUri, payloadSecret, projectDir, template } = args
if (cliArgs['--dry-run']) {
debug(`DRY RUN: .env file created`)
return
}
try {
if (template.type === 'starter' && fs.existsSync(path.join(projectDir, '.env.example'))) {
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
@@ -40,11 +47,9 @@ export async function writeEnvFile(args: {
// Write new .env file
await fs.writeFile(path.join(projectDir, '.env'), envWithValues.join('\n'))
} 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,19 +1,31 @@
import * as p from '@clack/prompts'
import slugify from '@sindresorhus/slugify'
import arg from 'arg'
import commandExists from 'command-exists'
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
@@ -23,13 +35,17 @@ export class Main {
this.args = arg(
{
'--db': String,
'--db-accept-recommended': Boolean,
'--db-connection-string': String,
'--help': Boolean,
'--local-template': String,
'--name': String,
'--secret': String,
'--template': String,
'--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,
@@ -55,41 +71,107 @@ export class Main {
async init(): Promise<void> {
try {
if (this.args['--help']) {
console.log(helpMessage())
helpMessage()
process.exit(0)
}
if (this.args['--init-next']) {
const result = await initNext(this.args)
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')
// 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))
const packageManager = await getPackageManager(this.args, projectDir)
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)
// 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)
}
}
console.log(welcomeMessage)
const projectName = await parseProjectName(this.args)
const validTemplates = getValidTemplates()
const template = await parseTemplate(this.args, validTemplates)
if (!template) {
p.log.error('Invalid template given')
p.outro(feedbackOutro())
process.exit(1)
}
const projectDir = projectName === '.' ? process.cwd() : `./${slugify(projectName)}`
const packageManager = await getPackageManager(this.args)
if (template.type !== 'plugin') {
const dbDetails = await selectDb(this.args, projectName)
const payloadSecret = generateSecret()
if (!this.args['--dry-run']) {
switch (template.type) {
case 'starter': {
const dbDetails = await selectDb(this.args, projectName)
const payloadSecret = generateSecret()
await createProject({
cliArgs: this.args,
dbDetails,
@@ -99,14 +181,15 @@ export class Main {
template,
})
await writeEnvFile({
cliArgs: this.args,
databaseUri: dbDetails.dbUri,
payloadSecret,
projectDir,
template,
})
break
}
} else {
if (!this.args['--dry-run']) {
case 'plugin': {
await createProject({
cliArgs: this.args,
packageManager,
@@ -114,18 +197,20 @@ export class Main {
projectName,
template,
})
break
}
}
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')
}
}
}
async function getPackageManager(args: CliArgs): Promise<PackageManager> {
async function getPackageManager(args: CliArgs, projectDir: string): Promise<PackageManager> {
let packageManager: PackageManager = 'npm'
if (args['--use-npm']) {
@@ -135,15 +220,8 @@ async function getPackageManager(args: CliArgs): Promise<PackageManager> {
} else if (args['--use-pnpm']) {
packageManager = 'pnpm'
} else {
try {
if (await commandExists('yarn')) {
packageManager = 'yarn'
} else if (await commandExists('pnpm')) {
packageManager = 'pnpm'
}
} catch (error: unknown) {
packageManager = 'npm'
}
const detected = await detect({ cwd: projectDir })
packageManager = detected || 'npm'
}
return packageManager
}

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

@@ -3,14 +3,18 @@ import type arg from 'arg'
export interface Args extends arg.Spec {
'--beta': BooleanConstructor
'--db': StringConstructor
'--db-accept-recommended': BooleanConstructor
'--db-connection-string': StringConstructor
'--debug': BooleanConstructor
'--dry-run': BooleanConstructor
'--help': BooleanConstructor
'--init-next': BooleanConstructor
'--local-template': StringConstructor
'--name': StringConstructor
'--no-deps': BooleanConstructor
'--secret': StringConstructor
'--template': StringConstructor
'--template-branch': StringConstructor
'--use-npm': BooleanConstructor
'--use-pnpm': BooleanConstructor
'--use-yarn': BooleanConstructor
@@ -50,7 +54,7 @@ interface Template {
type: ProjectTemplate['type']
}
export type PackageManager = 'npm' | 'pnpm' | 'yarn'
export type PackageManager = 'bun' | 'npm' | 'pnpm' | 'yarn'
export type DbType = 'mongodb' | 'postgres'
@@ -59,5 +63,4 @@ export type DbDetails = {
type: DbType
}
export type BundlerType = 'vite' | 'webpack'
export type EditorType = 'lexical' | 'slate'

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,26 +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'
import type { ProjectTemplate } from '../types.js'
import type { PackageManager } from '../types.js'
import { getValidTemplates } from '../lib/templates'
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,14 @@ 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 $} {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 +37,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 +46,58 @@ 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 relativePath = 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:')}
Move all files from ./${relativePath} to a named directory such as ./${relativePath}/${chalk.bold('(app)')}
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,19 +5,9 @@
"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",
"src/**/*.spec.js",
"src/**/*.spec.jsx",
"src/**/*.spec.ts",
"src/**/*.spec.tsx"
],
"include": ["src/**/*.ts", "src/**/*.tsx", "src/**/*.d.ts", "src/**/*.json"]
"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-alpha.61",
"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

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

@@ -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,13 +14,14 @@ 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)
@@ -54,7 +51,7 @@ 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
@@ -72,7 +69,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

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

View File

@@ -26,6 +26,7 @@ export const updateOne: UpdateOne = async function updateOne(
})
let result
try {
result = await Model.findOneAndUpdate(query, data, options)
} catch (error) {

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-alpha.61",
"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

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

@@ -1,47 +1,71 @@
import type { DeleteOne } from 'payload/database'
import type { PayloadRequest } from 'payload/types'
import toSnakeCase from 'to-snake-case'
import { eq } from 'drizzle-orm'
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(
this: PostgresAdapter,
{ collection, req = {} as PayloadRequest, where: incomingWhere },
{ collection: collectionSlug, req = {} as PayloadRequest, where: whereArg },
) {
const db = this.sessions[req.transactionID]?.db || this.drizzle
const collectionConfig = this.payload.collections[collection].config
const tableName = toSnakeCase(collection)
const { where } = await buildQuery({
const collection = this.payload.collections[collectionSlug].config
const tableName = getTableName({
adapter: this,
fields: collectionConfig.fields,
config: collection,
})
let docToDelete: Record<string, unknown>
const { joinAliases, joins, selectFields, where } = await buildQuery({
adapter: this,
fields: collection.fields,
locale: req.locale,
tableName,
where: incomingWhere,
where: whereArg,
})
const findManyArgs = buildFindManyArgs({
const selectDistinctResult = await selectDistinct({
adapter: this,
depth: 0,
fields: collectionConfig.fields,
chainedMethods: [{ args: [1], method: 'limit' }],
db,
joinAliases,
joins,
selectFields,
tableName,
where,
})
findManyArgs.where = where
if (selectDistinctResult?.[0]?.id) {
docToDelete = await db.query[tableName].findFirst({
where: eq(this.tables[tableName].id, selectDistinctResult[0].id),
})
} else {
const findManyArgs = buildFindManyArgs({
adapter: this,
depth: 0,
fields: collection.fields,
tableName,
})
const docToDelete = await db.query[tableName].findFirst(findManyArgs)
findManyArgs.where = where
docToDelete = await db.query[tableName].findFirst(findManyArgs)
}
const result = transform({
config: this.payload.config,
data: docToDelete,
fields: collectionConfig.fields,
fields: collection.fields,
})
await db.delete(this.tables[tableName]).where(where)
await db.delete(this.tables[tableName]).where(eq(this.tables[tableName].id, docToDelete.id))
return result
}

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

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

@@ -1,3 +1,5 @@
import type { QueryPromise } from 'drizzle-orm'
export type ChainedMethods = {
args: unknown[]
method: string
@@ -8,7 +10,7 @@ export type ChainedMethods = {
* @param methods
* @param query
*/
const chainMethods = ({ methods, query }): Promise<unknown> => {
const chainMethods = <T>({ methods, query }): QueryPromise<T> => {
return methods.reduce((query, { args, method }) => {
return query[method](...args)
}, query)

View File

@@ -7,6 +7,7 @@ import type { PostgresAdapter } from '../types.js'
import type { ChainedMethods } from './chainMethods.js'
import buildQuery from '../queries/buildQuery.js'
import { selectDistinct } from '../queries/selectDistinct.js'
import { transform } from '../transform/read/index.js'
import { buildFindManyArgs } from './buildFindManyArgs.js'
import { chainMethods } from './chainMethods.js'
@@ -39,7 +40,6 @@ export const findMany = async function find({
let hasPrevPage: boolean
let hasNextPage: boolean
let pagingCounter: number
let selectDistinctResult
const { joinAliases, joins, orderBy, selectFields, where } = await buildQuery({
adapter,
@@ -69,36 +69,21 @@ export const findMany = async function find({
tableName,
})
// only fetch IDs when a sort or where query is used that needs to be done on join tables, otherwise these can be done directly on the table in findMany
if (Object.keys(joins).length > 0 || joinAliases.length > 0) {
if (where) {
selectDistinctMethods.push({ args: [where], method: 'where' })
}
selectDistinctMethods.push({ args: [skip || (page - 1) * limit], method: 'offset' })
selectDistinctMethods.push({ args: [limit === 0 ? undefined : limit], method: 'limit' })
joinAliases.forEach(({ condition, table }) => {
selectDistinctMethods.push({
args: [table, condition],
method: 'leftJoin',
})
})
Object.entries(joins).forEach(([joinTable, condition]) => {
if (joinTable) {
selectDistinctMethods.push({
args: [adapter.tables[joinTable], condition],
method: 'leftJoin',
})
}
})
selectDistinctMethods.push({ args: [skip || (page - 1) * limit], method: 'offset' })
selectDistinctMethods.push({ args: [limit === 0 ? undefined : limit], method: 'limit' })
selectDistinctResult = await chainMethods({
methods: selectDistinctMethods,
query: db.selectDistinct(selectFields).from(table),
})
const selectDistinctResult = await selectDistinct({
adapter,
chainedMethods: selectDistinctMethods,
db,
joinAliases,
joins,
selectFields,
tableName,
where,
})
if (selectDistinctResult) {
if (selectDistinctResult.length === 0) {
return {
docs: [],
@@ -112,13 +97,14 @@ export const findMany = async function find({
totalDocs: 0,
totalPages: 0,
}
} else {
// set the id in an object for sorting later
selectDistinctResult.forEach(({ id }, i) => {
orderedIDMap[id] = i
})
orderedIDs = Object.keys(orderedIDMap)
findManyArgs.where = inArray(adapter.tables[tableName].id, orderedIDs)
}
// set the id in an object for sorting later
selectDistinctResult.forEach(({ id }, i) => {
orderedIDMap[id as number | string] = i
})
orderedIDs = Object.keys(orderedIDMap)
findManyArgs.where = inArray(adapter.tables[tableName].id, orderedIDs)
} else {
findManyArgs.limit = limitArg === 0 ? undefined : limitArg

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

@@ -40,6 +40,8 @@ import { updateVersion } from './updateVersion.js'
export type { MigrateDownArgs, MigrateUpArgs } from './types.js'
export { sql } from 'drizzle-orm'
export function postgresAdapter(args: Args): DatabaseAdapterObj<PostgresAdapter> {
function adapter({ payload }: { payload: Payload }) {
const migrationDir = findMigrationDir(args.migrationDir)
@@ -49,20 +51,24 @@ export function postgresAdapter(args: Args): DatabaseAdapterObj<PostgresAdapter>
name: 'postgres',
// Postgres-specific
blockTableNames: {},
drizzle: undefined,
enums: {},
fieldConstraints: {},
idType,
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,

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,
@@ -218,7 +226,12 @@ export const getTableColumnFromPath = ({
}
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 +278,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 +303,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 +374,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 +383,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 +440,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,
@@ -441,7 +491,7 @@ export const getTableColumnFromPath = ({
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`
newTableName = `${tableName}${adapter.localesSuffix}`
const parentTable = aliasTable || adapter.tables[tableName]

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))) {
@@ -85,6 +85,10 @@ export const sanitizeQueryValue = ({
}
}
if ('hasMany' in field && field.hasMany && operator === 'contains') {
operator = 'equals'
}
if (operator === 'near' || operator === 'within' || operator === 'intersects') {
throw new APIError(
`Querying with '${operator}' is not supported with the postgres database adapter.`,

View File

@@ -0,0 +1,60 @@
import type { QueryPromise, SQL } from 'drizzle-orm'
import type { ChainedMethods } from '../find/chainMethods.js'
import type { DrizzleDB, PostgresAdapter } from '../types.js'
import type { BuildQueryJoinAliases, BuildQueryJoins } from './buildQuery.js'
import { chainMethods } from '../find/chainMethods.js'
import { type GenericColumn } from '../types.js'
type Args = {
adapter: PostgresAdapter
chainedMethods?: ChainedMethods
db: DrizzleDB
joinAliases: BuildQueryJoinAliases
joins: BuildQueryJoins
selectFields: Record<string, GenericColumn>
tableName: string
where: SQL
}
/**
* Selects distinct records from a table only if there are joins that need to be used, otherwise return null
*/
export const selectDistinct = ({
adapter,
chainedMethods = [],
db,
joinAliases,
joins,
selectFields,
tableName,
where,
}: Args): QueryPromise<Record<string, GenericColumn> & { id: number | string }[]> => {
if (Object.keys(joins).length > 0 || joinAliases.length > 0) {
if (where) {
chainedMethods.push({ args: [where], method: 'where' })
}
joinAliases.forEach(({ condition, table }) => {
chainedMethods.push({
args: [table, condition],
method: 'leftJoin',
})
})
Object.entries(joins).forEach(([joinTable, condition]) => {
if (joinTable) {
chainedMethods.push({
args: [adapter.tables[joinTable], condition],
method: 'leftJoin',
})
}
})
return chainMethods({
methods: chainedMethods,
query: db.selectDistinct(selectFields).from(adapter.tables[tableName]),
})
}
}

View File

@@ -2,9 +2,9 @@ import type { PayloadRequest, SanitizedCollectionConfig } from 'payload/types'
import { type QueryDrafts, combineQueries } from 'payload/database'
import { buildVersionCollectionFields } from 'payload/versions'
import toSnakeCase from 'to-snake-case'
import { findMany } from './find/findMany.js'
import { getTableName } from './schema/getTableName.js'
export const queryDrafts: QueryDrafts = async function queryDrafts({
collection,
@@ -17,7 +17,11 @@ export const queryDrafts: QueryDrafts = async function queryDrafts({
where,
}) {
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 combinedWhere = combineQueries({ latest: { equals: true } }, where)

View File

@@ -11,10 +11,10 @@ import type { Field } from 'payload/types'
import { relations } from 'drizzle-orm'
import { index, integer, numeric, serial, timestamp, unique, varchar } from 'drizzle-orm/pg-core'
import { fieldAffectsData } from 'payload/types'
import toSnakeCase from 'to-snake-case'
import type { GenericColumns, GenericTable, IDType, PostgresAdapter } from '../types.js'
import { getTableName } from './getTableName.js'
import { parentIDColumnMap } from './parentIDColumnMap.js'
import { setColumnID } from './setColumnID.js'
import { traverseFields } from './traverseFields.js'
@@ -35,6 +35,7 @@ type Args = {
rootTableName?: string
tableName: string
timestamps?: boolean
versions: boolean
}
type Result = {
@@ -59,6 +60,7 @@ export const buildTable = ({
rootTableName: incomingRootTableName,
tableName,
timestamps,
versions,
}: Args): Result => {
const rootTableName = incomingRootTableName || tableName
const columns: Record<string, PgColumnBuilder> = baseColumns
@@ -113,6 +115,7 @@ export const buildTable = ({
rootRelationsToBuild: rootRelationsToBuild || relationsToBuild,
rootTableIDColType: rootTableIDColType || idColType,
rootTableName,
versions,
}))
if (timestamps) {
@@ -147,7 +150,7 @@ export const buildTable = ({
adapter.tables[tableName] = table
if (hasLocalizedField) {
const localeTableName = `${tableName}_locales`
const localeTableName = `${tableName}${adapter.localesSuffix}`
localesColumns.id = serial('id').primaryKey()
localesColumns._locale = adapter.enums.enum__locales('_locale').notNull()
localesColumns._parentID = parentIDColumnMap[idColType]('_parent_id')
@@ -288,11 +291,16 @@ export const buildTable = ({
}
relationships.forEach((relationTo) => {
const formattedRelationTo = toSnakeCase(relationTo)
const relationshipConfig = adapter.payload.collections[relationTo].config
const formattedRelationTo = getTableName({
adapter,
config: relationshipConfig,
throwValidationError: true,
})
let colType = adapter.idType === 'uuid' ? 'uuid' : 'integer'
const relatedCollectionCustomID = adapter.payload.collections[
relationTo
].config.fields.find((field) => fieldAffectsData(field) && field.name === 'id')
const relatedCollectionCustomID = relationshipConfig.fields.find(
(field) => fieldAffectsData(field) && field.name === 'id',
)
if (relatedCollectionCustomID?.type === 'number') colType = 'numeric'
if (relatedCollectionCustomID?.type === 'text') colType = 'varchar'
@@ -301,7 +309,7 @@ export const buildTable = ({
).references(() => adapter.tables[formattedRelationTo].id, { onDelete: 'cascade' })
})
const relationshipsTableName = `${tableName}_rels`
const relationshipsTableName = `${tableName}${adapter.relationshipsSuffix}`
relationshipsTable = adapter.pgSchema.table(
relationshipsTableName,
@@ -333,7 +341,11 @@ export const buildTable = ({
}
relationships.forEach((relationTo) => {
const relatedTableName = toSnakeCase(relationTo)
const relatedTableName = getTableName({
adapter,
config: adapter.payload.collections[relationTo].config,
throwValidationError: true,
})
const idColumnName = `${relationTo}ID`
result[idColumnName] = one(adapter.tables[relatedTableName], {
fields: [relationshipsTable[idColumnName]],

View File

@@ -0,0 +1,75 @@
import type { DBIdentifierName } from 'payload/database'
import { APIError } from 'payload/errors'
import toSnakeCase from 'to-snake-case'
import type { PostgresAdapter } from '../types.js'
type Args = {
adapter: PostgresAdapter
/** The collection, global or field config **/
config: {
dbName?: DBIdentifierName
enumName?: DBIdentifierName
name?: string
slug?: string
}
/** Localized tables need to be given the locales suffix */
locales?: boolean
/** For nested tables passed for the user custom dbName functions to handle their own iterations */
parentTableName?: string
/** For sub tables (array for example) this needs to include the parentTableName */
prefix?: string
/** Adds the relationships suffix */
relationships?: boolean
/** For tables based on fields that could have both enumName and dbName (ie: select with hasMany), default: 'dbName' */
target?: 'dbName' | 'enumName'
throwValidationError?: boolean
/** Adds the versions suffix, should only be used on the base collection to duplicate suffixing */
versions?: boolean
}
/**
* Used to name database enums and tables
* Returns the table or enum name for a given entity
*/
export const getTableName = ({
adapter,
config: { name, slug },
config,
locales = false,
parentTableName,
prefix = '',
relationships = false,
target = 'dbName',
throwValidationError = false,
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({ tableName: parentTableName }) : custom
} else {
result = `${prefix}${toSnakeCase(name ?? slug)}`
}
if (locales) result = `${result}${adapter.localesSuffix}`
if (versions) result = `_${result}${adapter.versionsSuffix}`
if (relationships) result = `${result}${adapter.relationshipsSuffix}`
if (!throwValidationError) {
return result
}
if (result.length > 63) {
throw new APIError(
`Exceeded max identifier length for table or enum name of 63 characters. Invalid name: ${result}`,
)
}
return result
}

View File

@@ -27,6 +27,7 @@ import type { GenericColumns, IDType, PostgresAdapter } from '../types.js'
import { hasLocalesTable } from '../utilities/hasLocalesTable.js'
import { buildTable } from './build.js'
import { createIndex } from './createIndex.js'
import { getTableName } from './getTableName.js'
import { idToUUID } from './idToUUID.js'
import { parentIDColumnMap } from './parentIDColumnMap.js'
import { validateExistingBlockIsIdentical } from './validateExistingBlockIsIdentical.js'
@@ -53,6 +54,7 @@ type Args = {
rootRelationsToBuild?: Map<string, string>
rootTableIDColType: string
rootTableName: string
versions: boolean
}
type Result = {
@@ -86,7 +88,9 @@ export const traverseFields = ({
rootRelationsToBuild,
rootTableIDColType,
rootTableName,
versions,
}: Args): Result => {
const throwValidationError = true
let hasLocalizedField = false
let hasLocalizedRelationshipField = false
let hasManyTextField: 'index' | boolean = false
@@ -217,7 +221,15 @@ export const traverseFields = ({
case 'radio':
case 'select': {
const enumName = `enum_${newTableName}_${toSnakeCase(field.name)}`
const enumName = getTableName({
adapter,
config: field,
parentTableName: newTableName,
prefix: `enum_${newTableName}_`,
target: 'enumName',
throwValidationError,
versions,
})
adapter.enums[enumName] = pgEnum(
enumName,
@@ -231,7 +243,14 @@ export const traverseFields = ({
)
if (field.type === 'select' && field.hasMany) {
const selectTableName = `${newTableName}_${toSnakeCase(field.name)}`
const selectTableName = getTableName({
adapter,
config: field,
parentTableName: newTableName,
prefix: `${newTableName}_`,
throwValidationError,
versions,
})
const baseColumns: Record<string, PgColumnBuilder> = {
order: integer('order').notNull(),
parent: parentIDColumnMap[parentIDColType]('parent_id')
@@ -266,6 +285,7 @@ export const traverseFields = ({
disableUnique,
fields: [],
tableName: selectTableName,
versions,
})
relationsToBuild.set(fieldName, selectTableName)
@@ -296,7 +316,13 @@ export const traverseFields = ({
case 'array': {
const disableNotNullFromHere = Boolean(field.admin?.condition) || disableNotNull
const arrayTableName = `${newTableName}_${toSnakeCase(field.name)}`
const arrayTableName = getTableName({
adapter,
config: field,
parentTableName: newTableName,
prefix: `${newTableName}_`,
throwValidationError,
})
const baseColumns: Record<string, PgColumnBuilder> = {
_order: integer('_order').notNull(),
_parentID: parentIDColumnMap[parentIDColType]('_parent_id')
@@ -334,6 +360,7 @@ export const traverseFields = ({
rootTableIDColType,
rootTableName,
tableName: arrayTableName,
versions,
})
if (subHasManyTextField) {
@@ -356,7 +383,7 @@ export const traverseFields = ({
}
if (hasLocalesTable(field.fields)) {
result._locales = many(adapter.tables[`${arrayTableName}_locales`])
result._locales = many(adapter.tables[`${arrayTableName}${adapter.localesSuffix}`])
}
subRelationsToBuild.forEach((val, key) => {
@@ -375,7 +402,13 @@ export const traverseFields = ({
const disableNotNullFromHere = Boolean(field.admin?.condition) || disableNotNull
field.blocks.forEach((block) => {
const blockTableName = `${rootTableName}_blocks_${toSnakeCase(block.slug)}`
const blockTableName = getTableName({
adapter,
config: block,
parentTableName: rootTableName,
prefix: `${rootTableName}_blocks_`,
throwValidationError,
})
if (!adapter.tables[blockTableName]) {
const baseColumns: Record<string, PgColumnBuilder> = {
_order: integer('_order').notNull(),
@@ -416,6 +449,7 @@ export const traverseFields = ({
rootTableIDColType,
rootTableName,
tableName: blockTableName,
versions,
})
if (subHasManyTextField) {
@@ -439,7 +473,9 @@ export const traverseFields = ({
}
if (hasLocalesTable(block.fields)) {
result._locales = many(adapter.tables[`${blockTableName}_locales`])
result._locales = many(
adapter.tables[`${blockTableName}${adapter.localesSuffix}`],
)
}
subRelationsToBuild.forEach((val, key) => {
@@ -451,7 +487,7 @@ export const traverseFields = ({
)
adapter.relations[`relations_${blockTableName}`] = blockTableRelations
} else if (process.env.NODE_ENV !== 'production') {
} else if (process.env.NODE_ENV !== 'production' && !versions) {
validateExistingBlockIsIdentical({
block,
localized: field.localized,
@@ -459,7 +495,7 @@ export const traverseFields = ({
table: adapter.tables[blockTableName],
})
}
adapter.blockTableNames[`${rootTableName}.${toSnakeCase(block.slug)}`] = blockTableName
rootRelationsToBuild.set(`_blocks_${block.slug}`, blockTableName)
})
@@ -498,6 +534,7 @@ export const traverseFields = ({
rootRelationsToBuild,
rootTableIDColType,
rootTableName,
versions,
})
if (groupHasLocalizedField) hasLocalizedField = true
@@ -540,6 +577,7 @@ export const traverseFields = ({
rootRelationsToBuild,
rootTableIDColType,
rootTableName,
versions,
})
if (groupHasLocalizedField) hasLocalizedField = true
@@ -583,6 +621,7 @@ export const traverseFields = ({
rootRelationsToBuild,
rootTableIDColType,
rootTableName,
versions,
})
if (tabHasLocalizedField) hasLocalizedField = true
@@ -626,6 +665,7 @@ export const traverseFields = ({
rootRelationsToBuild,
rootTableIDColType,
rootTableName,
versions,
})
if (rowHasLocalizedField) hasLocalizedField = true

View File

@@ -24,11 +24,14 @@ export type DrizzleDB = NodePgDatabase<Record<string, unknown>>
export type Args = {
idType?: 'serial' | 'uuid'
localesSuffix?: string
logger?: DrizzleConfig['logger']
migrationDir?: string
pool: PoolConfig
push?: boolean
relationshipsSuffix?: string
schemaName?: string
versionsSuffix?: string
}
export type GenericColumn = PgColumn<
@@ -58,6 +61,10 @@ export type DrizzleTransaction = PgTransaction<
>
export type PostgresAdapter = BaseDatabaseAdapter & {
/**
* Used internally to map the block name to the table name
*/
blockTableNames: Record<string, string>
drizzle: DrizzleDB
enums: Record<string, GenericEnum>
/**
@@ -66,12 +73,14 @@ export type PostgresAdapter = BaseDatabaseAdapter & {
*/
fieldConstraints: Record<string, Record<string, string>>
idType: Args['idType']
localesSuffix?: string
logger: DrizzleConfig['logger']
pgSchema?: { table: PgTableFn } | PgSchema
pool: Pool
poolOptions: Args['pool']
push: boolean
relations: Record<string, GenericRelation>
relationshipsSuffix?: string
schema: Record<string, GenericEnum | GenericRelation | GenericTable>
schemaName?: Args['schemaName']
sessions: {
@@ -82,6 +91,7 @@ export type PostgresAdapter = BaseDatabaseAdapter & {
}
}
tables: Record<string, GenericTable | PgTableWithColumns<any>>
versionsSuffix?: string
}
export type IDType = 'integer' | 'numeric' | 'uuid' | 'varchar'
@@ -98,9 +108,11 @@ declare module 'payload' {
drizzle: DrizzleDB
enums: Record<string, GenericEnum>
fieldConstraints: Record<string, Record<string, string>>
localeSuffix?: string
pool: Pool
push: boolean
relations: Record<string, GenericRelation>
relationshipsSuffix?: string
schema: Record<string, GenericEnum | GenericRelation | GenericTable>
sessions: {
[id: string]: {
@@ -110,5 +122,6 @@ declare module 'payload' {
}
}
tables: Record<string, GenericTable>
versionsSuffix?: string
}
}

View File

@@ -1,12 +1,10 @@
import type { UpdateOne } from 'payload/database'
import toSnakeCase from 'to-snake-case'
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 { selectDistinct } from './queries/selectDistinct.js'
import { getTableName } from './schema/getTableName.js'
import { upsertRow } from './upsertRow/index.js'
export const updateOne: UpdateOne = async function updateOne(
@@ -15,8 +13,12 @@ export const updateOne: UpdateOne = async function updateOne(
) {
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,
})
const whereToUse = whereArg || { id: { equals: id } }
let idToUpdate = id
const { joinAliases, joins, selectFields, where } = await buildQuery({
adapter: this,
@@ -26,42 +28,19 @@ export const updateOne: UpdateOne = async function updateOne(
where: whereToUse,
})
let idToUpdate = id
const selectDistinctResult = await selectDistinct({
adapter: this,
chainedMethods: [{ args: [1], method: 'limit' }],
db,
joinAliases,
joins,
selectFields,
tableName,
where,
})
// only fetch IDs when a sort or where query is used that needs to be done on join tables, otherwise these can be done directly on the table in findMany
if (Object.keys(joins).length > 0 || joinAliases.length > 0) {
const selectDistinctMethods: ChainedMethods = []
if (where) {
selectDistinctMethods.push({ args: [where], method: 'where' })
}
joinAliases.forEach(({ condition, table }) => {
selectDistinctMethods.push({
args: [table, condition],
method: 'leftJoin',
})
})
Object.entries(joins).forEach(([joinTable, condition]) => {
if (joinTable) {
selectDistinctMethods.push({
args: [this.tables[joinTable], condition],
method: 'leftJoin',
})
}
})
selectDistinctMethods.push({ args: [1], method: 'limit' })
const selectDistinctResult = await chainMethods({
methods: selectDistinctMethods,
query: db.selectDistinct(selectFields).from(this.tables[tableName]),
})
if (selectDistinctResult?.[0]?.id) {
idToUpdate = selectDistinctResult?.[0]?.id
}
if (selectDistinctResult?.[0]?.id) {
idToUpdate = selectDistinctResult?.[0]?.id
}
const result = await upsertRow({
@@ -72,7 +51,7 @@ export const updateOne: UpdateOne = async function updateOne(
fields: collection.fields,
operation: 'update',
req,
tableName: toSnakeCase(collectionSlug),
tableName,
})
return result

View File

@@ -1,10 +1,9 @@
import type { UpdateGlobalArgs } 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 updateGlobal<T extends TypeWithID>(
@@ -13,7 +12,10 @@ export async function updateGlobal<T extends TypeWithID>(
): Promise<T> {
const db = this.sessions[req.transactionID]?.db || this.drizzle
const globalConfig = this.payload.globals.config.find((config) => config.slug === slug)
const tableName = toSnakeCase(slug)
const tableName = getTableName({
adapter: this,
config: globalConfig,
})
const existingGlobal = await db.query[tableName].findFirst({})

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