@xleddyl/nuxt-cms 0.9.3 → 0.10.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +1 -1
- package/dist/module.d.mts +20 -1
- package/dist/module.json +1 -1
- package/dist/module.mjs +1037 -148
- package/dist/runtime/app/blocks/CmsBlocks.d.vue.ts +1 -1
- package/dist/runtime/app/blocks/CmsBlocks.vue +57 -9
- package/dist/runtime/app/blocks/CmsBlocks.vue.d.ts +1 -1
- package/dist/runtime/app/blocks/CmsField.d.ts +24 -0
- package/dist/runtime/app/blocks/CmsField.js +16 -0
- package/dist/runtime/app/blocks/node.d.ts +37 -0
- package/dist/runtime/app/blocks/node.js +17 -0
- package/dist/runtime/app/blocks/resolve.d.ts +18 -0
- package/dist/runtime/app/blocks/resolve.js +34 -0
- package/dist/runtime/app/components/cms/BlockPicker.d.vue.ts +22 -0
- package/dist/runtime/app/components/cms/BlockPicker.vue +87 -0
- package/dist/runtime/app/components/cms/BlockPicker.vue.d.ts +22 -0
- package/dist/runtime/app/components/cms/BlockPickerPopover.d.vue.ts +38 -0
- package/dist/runtime/app/components/cms/BlockPickerPopover.vue +44 -0
- package/dist/runtime/app/components/cms/BlockPickerPopover.vue.d.ts +38 -0
- package/dist/runtime/app/components/cms/BlockTree.d.vue.ts +29 -0
- package/dist/runtime/app/components/cms/BlockTree.vue +322 -0
- package/dist/runtime/app/components/cms/BlockTree.vue.d.ts +29 -0
- package/dist/runtime/app/components/cms/BlocksField.vue +569 -218
- package/dist/runtime/app/components/cms/BrandMark.vue +7 -13
- package/dist/runtime/app/components/cms/ConfirmGroups.vue +15 -13
- package/dist/runtime/app/components/cms/{BlockPalette.vue.d.ts → EditorHistory.d.vue.ts} +6 -5
- package/dist/runtime/app/components/cms/EditorHistory.vue +34 -0
- package/dist/runtime/app/components/cms/{BlockPalette.d.vue.ts → EditorHistory.vue.d.ts} +6 -5
- package/dist/runtime/app/components/cms/EntryDrawer.d.vue.ts +11 -5
- package/dist/runtime/app/components/cms/EntryDrawer.vue +183 -152
- package/dist/runtime/app/components/cms/EntryDrawer.vue.d.ts +11 -5
- package/dist/runtime/app/components/cms/EntryForm.d.vue.ts +16 -4
- package/dist/runtime/app/components/cms/EntryForm.vue +106 -146
- package/dist/runtime/app/components/cms/EntryForm.vue.d.ts +16 -4
- package/dist/runtime/app/components/cms/FieldInput.vue +10 -2
- package/dist/runtime/app/components/cms/FormSections.d.vue.ts +22 -0
- package/dist/runtime/app/components/cms/FormSections.vue +71 -0
- package/dist/runtime/app/components/cms/FormSections.vue.d.ts +22 -0
- package/dist/runtime/app/components/cms/ImportModal.vue +46 -58
- package/dist/runtime/app/components/cms/LinkField.d.vue.ts +16 -0
- package/dist/runtime/app/components/cms/LinkField.vue +250 -0
- package/dist/runtime/app/components/cms/LinkField.vue.d.ts +16 -0
- package/dist/runtime/app/components/cms/ListFilters.vue +10 -12
- package/dist/runtime/app/components/cms/LocaleSwitch.d.vue.ts +2 -1
- package/dist/runtime/app/components/cms/LocaleSwitch.vue +29 -5
- package/dist/runtime/app/components/cms/LocaleSwitch.vue.d.ts +2 -1
- package/dist/runtime/app/components/cms/MediaDetail.d.vue.ts +2 -0
- package/dist/runtime/app/components/cms/MediaDetail.vue +112 -83
- package/dist/runtime/app/components/cms/MediaDetail.vue.d.ts +2 -0
- package/dist/runtime/app/components/cms/MediaField.vue +35 -25
- package/dist/runtime/app/components/cms/MediaFolderPicker.vue +26 -71
- package/dist/runtime/app/components/cms/MediaGallery.d.vue.ts +1 -0
- package/dist/runtime/app/components/cms/MediaGallery.vue +442 -419
- package/dist/runtime/app/components/cms/MediaGallery.vue.d.ts +1 -0
- package/dist/runtime/app/components/cms/MediaThumb.vue +9 -25
- package/dist/runtime/app/components/cms/MediaUpload.vue +1 -1
- package/dist/runtime/app/components/cms/{BlockOutline.d.vue.ts → NestedBlockList.d.vue.ts} +5 -5
- package/dist/runtime/app/components/cms/NestedBlockList.vue +87 -0
- package/dist/runtime/app/components/cms/{BlockOutline.vue.d.ts → NestedBlockList.vue.d.ts} +5 -5
- package/dist/runtime/app/components/cms/PoweredBy.vue +12 -5
- package/dist/runtime/app/components/cms/PresetModal.d.vue.ts +18 -0
- package/dist/runtime/app/components/cms/PresetModal.vue +70 -0
- package/dist/runtime/app/components/cms/PresetModal.vue.d.ts +18 -0
- package/dist/runtime/app/components/cms/RelationField.vue +2 -8
- package/dist/runtime/app/components/cms/SettingsAccount.vue +60 -71
- package/dist/runtime/app/components/cms/SettingsColumns.vue +47 -69
- package/dist/runtime/app/components/cms/SettingsEmptyAdd.d.vue.ts +13 -0
- package/dist/runtime/app/components/cms/SettingsEmptyAdd.vue +21 -0
- package/dist/runtime/app/components/cms/SettingsEmptyAdd.vue.d.ts +13 -0
- package/dist/runtime/app/components/cms/SettingsEntries.d.vue.ts +13 -0
- package/dist/runtime/app/components/cms/SettingsEntries.vue +50 -0
- package/dist/runtime/app/components/cms/SettingsEntries.vue.d.ts +13 -0
- package/dist/runtime/app/components/cms/SettingsEntry.d.vue.ts +1 -10
- package/dist/runtime/app/components/cms/SettingsEntry.vue +11 -95
- package/dist/runtime/app/components/cms/SettingsEntry.vue.d.ts +1 -10
- package/dist/runtime/app/components/cms/SettingsEntryActions.d.vue.ts +7 -0
- package/dist/runtime/app/components/cms/SettingsEntryActions.vue +42 -0
- package/dist/runtime/app/components/cms/SettingsEntryActions.vue.d.ts +7 -0
- package/dist/runtime/app/components/cms/SettingsEntryTabs.d.vue.ts +7 -0
- package/dist/runtime/app/components/cms/SettingsEntryTabs.vue +21 -0
- package/dist/runtime/app/components/cms/SettingsEntryTabs.vue.d.ts +7 -0
- package/dist/runtime/app/components/cms/SettingsFormAction.d.vue.ts +3 -0
- package/dist/runtime/app/components/cms/SettingsFormAction.vue +17 -0
- package/dist/runtime/app/components/cms/SettingsFormAction.vue.d.ts +3 -0
- package/dist/runtime/app/components/cms/SettingsLayout.vue +18 -14
- package/dist/runtime/app/components/cms/SettingsPresetForm.d.vue.ts +11 -0
- package/dist/runtime/app/components/cms/SettingsPresetForm.vue +60 -0
- package/dist/runtime/app/components/cms/SettingsPresetForm.vue.d.ts +11 -0
- package/dist/runtime/app/components/cms/SettingsPresets.d.vue.ts +13 -0
- package/dist/runtime/app/components/cms/SettingsPresets.vue +65 -0
- package/dist/runtime/app/components/cms/SettingsPresets.vue.d.ts +13 -0
- package/dist/runtime/app/components/cms/SettingsRecordActions.d.vue.ts +32 -0
- package/dist/runtime/app/components/cms/SettingsRecordActions.vue +45 -0
- package/dist/runtime/app/components/cms/SettingsRecordActions.vue.d.ts +32 -0
- package/dist/runtime/app/components/cms/SettingsRedirectActions.d.vue.ts +3 -0
- package/dist/runtime/app/components/cms/SettingsRedirectActions.vue +59 -0
- package/dist/runtime/app/components/cms/SettingsRedirectActions.vue.d.ts +3 -0
- package/dist/runtime/app/components/cms/SettingsRedirectForm.d.vue.ts +11 -0
- package/dist/runtime/app/components/cms/SettingsRedirectForm.vue +77 -0
- package/dist/runtime/app/components/cms/SettingsRedirectForm.vue.d.ts +11 -0
- package/dist/runtime/app/components/cms/SettingsRedirects.d.vue.ts +13 -0
- package/dist/runtime/app/components/cms/SettingsRedirects.vue +117 -0
- package/dist/runtime/app/components/cms/SettingsRedirects.vue.d.ts +13 -0
- package/dist/runtime/app/components/cms/SettingsSecretCard.d.vue.ts +17 -0
- package/dist/runtime/app/components/cms/SettingsSecretCard.vue +43 -0
- package/dist/runtime/app/components/cms/SettingsSecretCard.vue.d.ts +17 -0
- package/dist/runtime/app/components/cms/SettingsSite.d.vue.ts +3 -0
- package/dist/runtime/app/components/cms/SettingsSite.vue +140 -0
- package/dist/runtime/app/components/cms/SettingsSite.vue.d.ts +3 -0
- package/dist/runtime/app/components/cms/SettingsUserCreate.d.vue.ts +8 -0
- package/dist/runtime/app/components/cms/SettingsUserCreate.vue +82 -0
- package/dist/runtime/app/components/cms/SettingsUserCreate.vue.d.ts +8 -0
- package/dist/runtime/app/components/cms/SettingsUserDetail.d.vue.ts +11 -0
- package/dist/runtime/app/components/cms/SettingsUserDetail.vue +179 -0
- package/dist/runtime/app/components/cms/SettingsUserDetail.vue.d.ts +11 -0
- package/dist/runtime/app/components/cms/SettingsUsers.d.vue.ts +9 -6
- package/dist/runtime/app/components/cms/SettingsUsers.vue +88 -234
- package/dist/runtime/app/components/cms/SettingsUsers.vue.d.ts +9 -6
- package/dist/runtime/app/components/cms/SharePreview.d.vue.ts +18 -0
- package/dist/runtime/app/components/cms/SharePreview.vue +219 -0
- package/dist/runtime/app/components/cms/SharePreview.vue.d.ts +18 -0
- package/dist/runtime/app/components/cms/SlugField.vue +5 -3
- package/dist/runtime/app/components/cms/StatusBadge.vue +9 -8
- package/dist/runtime/app/components/cms/TranslationBadges.d.vue.ts +7 -0
- package/dist/runtime/app/components/cms/TranslationBadges.vue +45 -0
- package/dist/runtime/app/components/cms/TranslationBadges.vue.d.ts +7 -0
- package/dist/runtime/app/components/cms/TranslationMenu.d.vue.ts +17 -0
- package/dist/runtime/app/components/cms/TranslationMenu.vue +28 -0
- package/dist/runtime/app/components/cms/TranslationMenu.vue.d.ts +17 -0
- package/dist/runtime/app/components/cms/VisualEditor.d.vue.ts +22 -4
- package/dist/runtime/app/components/cms/VisualEditor.vue +1152 -335
- package/dist/runtime/app/components/cms/VisualEditor.vue.d.ts +22 -4
- package/dist/runtime/app/components/cms/WelcomeModal.vue +32 -50
- package/dist/runtime/app/composables/cms-clipboard.d.ts +8 -0
- package/dist/runtime/app/composables/cms-clipboard.js +38 -0
- package/dist/runtime/app/composables/cms-confirm.d.ts +6 -8
- package/dist/runtime/app/composables/cms-confirm.js +5 -7
- package/dist/runtime/app/composables/cms-entry-disabled.d.ts +5 -1
- package/dist/runtime/app/composables/cms-entry-disabled.js +7 -0
- package/dist/runtime/app/composables/cms-entry-form.d.ts +15 -0
- package/dist/runtime/app/composables/cms-entry-form.js +40 -0
- package/dist/runtime/app/composables/cms-entry-settings.d.ts +30 -0
- package/dist/runtime/app/composables/cms-entry-settings.js +122 -0
- package/dist/runtime/app/composables/cms-entry.d.ts +5 -1
- package/dist/runtime/app/composables/cms-entry.js +36 -11
- package/dist/runtime/app/composables/cms-history.d.ts +10 -0
- package/dist/runtime/app/composables/cms-history.js +24 -0
- package/dist/runtime/app/composables/cms-link-targets.d.ts +10 -0
- package/dist/runtime/app/composables/cms-link-targets.js +40 -0
- package/dist/runtime/app/composables/cms-link-usage.d.ts +1 -0
- package/dist/runtime/app/composables/cms-link-usage.js +45 -0
- package/dist/runtime/app/composables/cms-media-library.d.ts +19 -9
- package/dist/runtime/app/composables/cms-media-library.js +31 -13
- package/dist/runtime/app/composables/cms-media-uploader.js +6 -20
- package/dist/runtime/app/composables/cms-permissions.d.ts +18 -0
- package/dist/runtime/app/composables/cms-permissions.js +54 -0
- package/dist/runtime/app/composables/cms-presets.d.ts +11 -0
- package/dist/runtime/app/composables/cms-presets.js +40 -0
- package/dist/runtime/app/composables/cms-preview-mode.d.ts +4 -0
- package/dist/runtime/app/composables/cms-preview-mode.js +8 -0
- package/dist/runtime/app/composables/cms-query.d.ts +3 -0
- package/dist/runtime/app/composables/cms-query.js +45 -20
- package/dist/runtime/app/composables/cms-record-list.d.ts +31 -0
- package/dist/runtime/app/composables/cms-record-list.js +75 -0
- package/dist/runtime/app/composables/cms-route.d.ts +6 -0
- package/dist/runtime/app/composables/cms-route.js +36 -0
- package/dist/runtime/app/composables/cms-runtime.d.ts +1 -0
- package/dist/runtime/app/composables/cms-save-handoff.d.ts +4 -0
- package/dist/runtime/app/composables/cms-save-handoff.js +19 -0
- package/dist/runtime/app/composables/cms-seo.d.ts +12 -0
- package/dist/runtime/app/composables/cms-seo.js +69 -0
- package/dist/runtime/app/composables/cms-settings-form.d.ts +13 -0
- package/dist/runtime/app/composables/cms-settings-form.js +29 -0
- package/dist/runtime/app/composables/cms-settings-sections.d.ts +26 -0
- package/dist/runtime/app/composables/cms-settings-sections.js +181 -0
- package/dist/runtime/app/composables/cms-settings-stores.d.ts +52 -0
- package/dist/runtime/app/composables/cms-settings-stores.js +156 -0
- package/dist/runtime/app/composables/cms-settings.d.ts +3 -3
- package/dist/runtime/app/composables/cms-settings.js +14 -8
- package/dist/runtime/app/composables/cms-site-disabled.d.ts +7 -0
- package/dist/runtime/app/composables/cms-site-disabled.js +8 -0
- package/dist/runtime/app/composables/cms-site.d.ts +7 -0
- package/dist/runtime/app/composables/cms-site.js +22 -0
- package/dist/runtime/app/composables/cms-toast.d.ts +2 -8
- package/dist/runtime/app/composables/cms-toast.js +3 -9
- package/dist/runtime/app/composables/cms-translation-actions.d.ts +15 -0
- package/dist/runtime/app/composables/cms-translation-actions.js +108 -0
- package/dist/runtime/app/css-url.d.ts +4 -0
- package/dist/runtime/app/image/CmsImage.d.vue.ts +89 -0
- package/dist/runtime/app/image/CmsImage.vue +91 -0
- package/dist/runtime/app/image/CmsImage.vue.d.ts +89 -0
- package/dist/runtime/app/layouts/cms-admin.d.vue.ts +2 -2
- package/dist/runtime/app/layouts/cms-admin.vue +98 -76
- package/dist/runtime/app/layouts/cms-admin.vue.d.ts +2 -2
- package/dist/runtime/app/layouts/cms-editor.vue +7 -2
- package/dist/runtime/app/links/CmsLink.d.vue.ts +18 -0
- package/dist/runtime/app/links/CmsLink.vue +18 -0
- package/dist/runtime/app/links/CmsLink.vue.d.ts +18 -0
- package/dist/runtime/app/middleware/cms-auth.js +13 -1
- package/dist/runtime/app/pages/admin-collection.vue +473 -297
- package/dist/runtime/app/pages/admin-content.vue +190 -53
- package/dist/runtime/app/pages/admin-entry.vue +79 -75
- package/dist/runtime/app/pages/admin-index.vue +18 -5
- package/dist/runtime/app/pages/admin-login.vue +1 -1
- package/dist/runtime/app/pages/admin-logout.vue +1 -1
- package/dist/runtime/app/pages/admin-media.vue +3 -3
- package/dist/runtime/app/pages/admin-preview.vue +112 -24
- package/dist/runtime/app/plugins/cms-field.d.ts +2 -0
- package/dist/runtime/app/plugins/cms-field.js +8 -0
- package/dist/runtime/app/plugins/cms-preview-banner.client.d.ts +2 -0
- package/dist/runtime/app/plugins/cms-preview-banner.client.js +19 -0
- package/dist/runtime/app/plugins/cms-preview.d.ts +2 -0
- package/dist/runtime/app/plugins/cms-preview.js +27 -0
- package/dist/runtime/app/preview/CmsPreviewBanner.d.vue.ts +7 -0
- package/dist/runtime/app/preview/CmsPreviewBanner.vue +63 -0
- package/dist/runtime/app/preview/CmsPreviewBanner.vue.d.ts +7 -0
- package/dist/runtime/app/preview/PreviewOverlay.d.vue.ts +17 -3
- package/dist/runtime/app/preview/PreviewOverlay.vue +629 -166
- package/dist/runtime/app/preview/PreviewOverlay.vue.d.ts +17 -3
- package/dist/runtime/app/preview/field-marker.d.ts +12 -0
- package/dist/runtime/app/preview/field-marker.js +20 -0
- package/dist/runtime/app/preview/inline-text.d.ts +15 -0
- package/dist/runtime/app/preview/inline-text.js +158 -0
- package/dist/runtime/app/utils/block-editor.d.ts +22 -0
- package/dist/runtime/app/utils/block-editor.js +12 -0
- package/dist/runtime/app/utils/keyboard.d.ts +6 -0
- package/dist/runtime/app/utils/keyboard.js +52 -0
- package/dist/runtime/app/utils/redirects.d.ts +6 -0
- package/dist/runtime/app/utils/redirects.js +7 -0
- package/dist/runtime/app/utils/ui.d.ts +8 -0
- package/dist/runtime/app/utils/ui.js +15 -1
- package/dist/runtime/app/utils/users.d.ts +15 -0
- package/dist/runtime/app/utils/users.js +14 -0
- package/dist/runtime/assets/cms.css +1 -1
- package/dist/runtime/generate.js +16 -2
- package/dist/runtime/server/api/collection-export.get.js +3 -2
- package/dist/runtime/server/api/collection-import.post.js +39 -11
- package/dist/runtime/server/api/collection.get.js +29 -10
- package/dist/runtime/server/api/collection.post.js +28 -11
- package/dist/runtime/server/api/collection.put.js +35 -13
- package/dist/runtime/server/api/item.delete.js +19 -7
- package/dist/runtime/server/api/item.get.js +3 -2
- package/dist/runtime/server/api/item.put.js +52 -18
- package/dist/runtime/server/api/link-usage.post.d.ts +5 -0
- package/dist/runtime/server/api/link-usage.post.js +21 -0
- package/dist/runtime/server/api/media-delete.post.js +4 -2
- package/dist/runtime/server/api/media-download.get.js +2 -2
- package/dist/runtime/server/api/media-folder.delete.js +8 -2
- package/dist/runtime/server/api/media-folder.patch.js +10 -2
- package/dist/runtime/server/api/media-folder.post.js +4 -2
- package/dist/runtime/server/api/media-move.post.js +7 -4
- package/dist/runtime/server/api/media-presign.post.js +2 -2
- package/dist/runtime/server/api/media-upload.put.js +2 -2
- package/dist/runtime/server/api/media-usage.post.d.ts +2 -4
- package/dist/runtime/server/api/media-usage.post.js +13 -9
- package/dist/runtime/server/api/media.delete.js +4 -2
- package/dist/runtime/server/api/media.get.js +22 -7
- package/dist/runtime/server/api/media.post.js +4 -2
- package/dist/runtime/server/api/media.put.js +33 -16
- package/dist/runtime/server/api/preset.delete.d.ts +4 -0
- package/dist/runtime/server/api/preset.delete.js +9 -0
- package/dist/runtime/server/api/preset.put.d.ts +3 -0
- package/dist/runtime/server/api/preset.put.js +23 -0
- package/dist/runtime/server/api/presets-field.get.d.ts +3 -0
- package/dist/runtime/server/api/presets-field.get.js +13 -0
- package/dist/runtime/server/api/presets-field.post.d.ts +3 -0
- package/dist/runtime/server/api/presets-field.post.js +36 -0
- package/dist/runtime/server/api/presets.get.d.ts +3 -0
- package/dist/runtime/server/api/presets.get.js +7 -0
- package/dist/runtime/server/api/preview-link.delete.d.ts +4 -0
- package/dist/runtime/server/api/preview-link.delete.js +15 -0
- package/dist/runtime/server/api/preview-links.get.d.ts +3 -0
- package/dist/runtime/server/api/preview-links.get.js +10 -0
- package/dist/runtime/server/api/preview-links.post.d.ts +3 -0
- package/dist/runtime/server/api/preview-links.post.js +17 -0
- package/dist/runtime/server/api/preview.get.d.ts +3 -0
- package/dist/runtime/server/api/preview.get.js +7 -0
- package/dist/runtime/server/api/redirect.delete.d.ts +4 -0
- package/dist/runtime/server/api/redirect.delete.js +10 -0
- package/dist/runtime/server/api/redirect.put.d.ts +3 -0
- package/dist/runtime/server/api/redirect.put.js +8 -0
- package/dist/runtime/server/api/redirects-import.post.d.ts +3 -0
- package/dist/runtime/server/api/redirects-import.post.js +13 -0
- package/dist/runtime/server/api/redirects.get.d.ts +3 -0
- package/dist/runtime/server/api/redirects.get.js +7 -0
- package/dist/runtime/server/api/redirects.post.d.ts +3 -0
- package/dist/runtime/server/api/redirects.post.js +7 -0
- package/dist/runtime/server/api/search.get.js +8 -3
- package/dist/runtime/server/api/settings.delete.js +2 -2
- package/dist/runtime/server/api/settings.get.js +4 -2
- package/dist/runtime/server/api/settings.put.js +2 -2
- package/dist/runtime/server/api/site.get.d.ts +3 -0
- package/dist/runtime/server/api/site.get.js +7 -0
- package/dist/runtime/server/api/site.put.d.ts +3 -0
- package/dist/runtime/server/api/site.put.js +16 -0
- package/dist/runtime/server/api/tags.get.js +3 -2
- package/dist/runtime/server/api/user.delete.js +8 -2
- package/dist/runtime/server/api/user.put.js +14 -4
- package/dist/runtime/server/api/users.get.js +2 -2
- package/dist/runtime/server/api/users.post.js +8 -4
- package/dist/runtime/server/middleware/hidden-site.d.ts +2 -0
- package/dist/runtime/server/middleware/hidden-site.js +8 -0
- package/dist/runtime/server/middleware/preview-links.d.ts +2 -0
- package/dist/runtime/server/middleware/preview-links.js +6 -0
- package/dist/runtime/server/middleware/redirects.d.ts +2 -0
- package/dist/runtime/server/middleware/redirects.js +18 -0
- package/dist/runtime/server/plugins/session-validate.js +4 -1
- package/dist/runtime/server/plugins/webhooks.d.ts +3 -0
- package/dist/runtime/server/plugins/webhooks.js +27 -0
- package/dist/runtime/server/routes/graphql.js +5 -3
- package/dist/runtime/server/routes/robots.d.ts +2 -0
- package/dist/runtime/server/routes/robots.js +16 -0
- package/dist/runtime/server/routes/sitemap.d.ts +2 -0
- package/dist/runtime/server/routes/sitemap.js +67 -0
- package/dist/runtime/server/utils/cached-value.d.ts +5 -0
- package/dist/runtime/server/utils/cached-value.js +24 -0
- package/dist/runtime/server/utils/content-routes.d.ts +5 -0
- package/dist/runtime/server/utils/content-routes.js +29 -0
- package/dist/runtime/server/utils/field-rules.d.ts +19 -0
- package/dist/runtime/server/utils/field-rules.js +72 -0
- package/dist/runtime/server/utils/graphql.d.ts +3 -1
- package/dist/runtime/server/utils/graphql.js +145 -29
- package/dist/runtime/server/utils/hooks.d.ts +14 -0
- package/dist/runtime/server/utils/hooks.js +71 -0
- package/dist/runtime/server/utils/import.d.ts +7 -2
- package/dist/runtime/server/utils/import.js +108 -14
- package/dist/runtime/server/utils/link-usage.d.ts +4 -0
- package/dist/runtime/server/utils/link-usage.js +28 -0
- package/dist/runtime/server/utils/media-references.d.ts +6 -1
- package/dist/runtime/server/utils/media-references.js +49 -20
- package/dist/runtime/server/utils/media-usage.d.ts +2 -0
- package/dist/runtime/server/utils/media-usage.js +5 -3
- package/dist/runtime/server/utils/page-rows.d.ts +2 -0
- package/dist/runtime/server/utils/page-rows.js +42 -29
- package/dist/runtime/server/utils/permissions.d.ts +14 -0
- package/dist/runtime/server/utils/permissions.js +43 -0
- package/dist/runtime/server/utils/presets.d.ts +18 -0
- package/dist/runtime/server/utils/presets.js +68 -0
- package/dist/runtime/server/utils/preview-links.d.ts +207 -0
- package/dist/runtime/server/utils/preview-links.js +161 -0
- package/dist/runtime/server/utils/redirects.d.ts +31 -0
- package/dist/runtime/server/utils/redirects.js +240 -0
- package/dist/runtime/server/utils/registry.js +4 -1
- package/dist/runtime/server/utils/relations.d.ts +8 -0
- package/dist/runtime/server/utils/relations.js +25 -6
- package/dist/runtime/server/utils/require-admin.d.ts +0 -1
- package/dist/runtime/server/utils/require-admin.js +4 -8
- package/dist/runtime/server/utils/search.d.ts +1 -0
- package/dist/runtime/server/utils/search.js +44 -19
- package/dist/runtime/server/utils/single-row.d.ts +3 -0
- package/dist/runtime/server/utils/single-row.js +4 -0
- package/dist/runtime/server/utils/site.d.ts +23 -0
- package/dist/runtime/server/utils/site.js +79 -0
- package/dist/runtime/server/utils/sitemap.d.ts +12 -0
- package/dist/runtime/server/utils/sitemap.js +109 -0
- package/dist/runtime/server/utils/user-management.d.ts +4 -0
- package/dist/runtime/server/utils/user-management.js +20 -0
- package/dist/runtime/server/utils/users.d.ts +1 -1
- package/dist/runtime/server/utils/users.js +1 -1
- package/dist/runtime/server/utils/visibility.d.ts +1 -0
- package/dist/runtime/server/utils/visibility.js +6 -1
- package/dist/runtime/server/utils/webhooks.d.ts +24 -0
- package/dist/runtime/server/utils/webhooks.js +90 -0
- package/dist/runtime/shared/block-paths.d.ts +66 -0
- package/dist/runtime/shared/block-paths.js +310 -0
- package/dist/runtime/shared/block-picker.d.ts +18 -0
- package/dist/runtime/shared/block-picker.js +31 -0
- package/dist/runtime/shared/block-signature.d.ts +4 -0
- package/dist/runtime/shared/block-signature.js +46 -0
- package/dist/runtime/shared/clipboard.d.ts +31 -0
- package/dist/runtime/shared/clipboard.js +206 -0
- package/dist/runtime/shared/conditions.d.ts +36 -0
- package/dist/runtime/shared/conditions.js +100 -0
- package/dist/runtime/shared/content-routes.d.ts +42 -0
- package/dist/runtime/shared/content-routes.js +237 -0
- package/dist/runtime/shared/duplicate.js +2 -1
- package/dist/runtime/shared/editor-layout.d.ts +18 -0
- package/dist/runtime/shared/editor-layout.js +9 -0
- package/dist/runtime/shared/field-options.d.ts +21 -0
- package/dist/runtime/shared/field-options.js +90 -0
- package/dist/runtime/shared/graphql-sdl.d.ts +1 -0
- package/dist/runtime/shared/graphql-sdl.js +50 -26
- package/dist/runtime/shared/history.d.ts +27 -0
- package/dist/runtime/shared/history.js +123 -0
- package/dist/runtime/shared/hooks.d.ts +45 -0
- package/dist/runtime/shared/hooks.js +28 -0
- package/dist/runtime/shared/index.d.ts +90 -21
- package/dist/runtime/shared/index.js +121 -37
- package/dist/runtime/shared/inline-text.d.ts +38 -0
- package/dist/runtime/shared/inline-text.js +85 -0
- package/dist/runtime/shared/layout.d.ts +2 -1
- package/dist/runtime/shared/layout.js +3 -0
- package/dist/runtime/shared/links.d.ts +48 -0
- package/dist/runtime/shared/links.js +155 -0
- package/dist/runtime/shared/list-filters.d.ts +2 -0
- package/dist/runtime/shared/list-filters.js +2 -0
- package/dist/runtime/shared/media-focal.d.ts +30 -0
- package/dist/runtime/shared/media-focal.js +52 -0
- package/dist/runtime/shared/migrations-dir.d.ts +14 -0
- package/dist/runtime/shared/migrations-dir.js +48 -2
- package/dist/runtime/shared/presets.d.ts +67 -0
- package/dist/runtime/shared/presets.js +127 -0
- package/dist/runtime/shared/preview-links.d.ts +64 -0
- package/dist/runtime/shared/preview-links.js +54 -0
- package/dist/runtime/shared/preview.d.ts +124 -21
- package/dist/runtime/shared/preview.js +419 -81
- package/dist/runtime/shared/redirects.d.ts +75 -0
- package/dist/runtime/shared/redirects.js +230 -0
- package/dist/runtime/shared/roles.d.ts +57 -0
- package/dist/runtime/shared/roles.js +293 -0
- package/dist/runtime/shared/save-state.d.ts +8 -0
- package/dist/runtime/shared/save-state.js +15 -0
- package/dist/runtime/shared/search.d.ts +16 -1
- package/dist/runtime/shared/search.js +41 -7
- package/dist/runtime/shared/seo.d.ts +167 -0
- package/dist/runtime/shared/seo.js +335 -0
- package/dist/runtime/shared/transfer.js +2 -1
- package/dist/runtime/shared/translations.d.ts +29 -0
- package/dist/runtime/shared/translations.js +123 -0
- package/dist/runtime/shared/users.d.ts +1 -1
- package/dist/runtime/shared/validation.d.ts +23 -1
- package/dist/runtime/shared/validation.js +214 -64
- package/dist/runtime/webhooks.d.ts +33 -0
- package/dist/runtime/webhooks.js +57 -0
- package/dist/types.d.mts +5 -1
- package/package.json +6 -2
- package/dist/runtime/app/components/cms/BlockOutline.vue +0 -88
- package/dist/runtime/app/components/cms/BlockPalette.vue +0 -31
- package/dist/runtime/app/components/cms/SettingsModal.d.vue.ts +0 -12
- package/dist/runtime/app/components/cms/SettingsModal.vue +0 -126
- package/dist/runtime/app/components/cms/SettingsModal.vue.d.ts +0 -12
- package/dist/runtime/app/composables/cms-field-focus.d.ts +0 -4
- package/dist/runtime/app/composables/cms-field-focus.js +0 -20
package/dist/module.mjs
CHANGED
|
@@ -3,18 +3,23 @@ import { existsSync, readFileSync } from 'node:fs';
|
|
|
3
3
|
import { createRequire } from 'node:module';
|
|
4
4
|
import { extname, resolve, isAbsolute, join, relative, dirname } from 'node:path';
|
|
5
5
|
import { pathToFileURL, fileURLToPath } from 'node:url';
|
|
6
|
-
import { defineNuxtModule, createResolver, useLogger, addImports, addTemplate, addServerPlugin, addTypeTemplate, addComponentsDir, tryResolveModule, addRouteMiddleware, extendPages, addServerHandler, resolvePath, addComponent } from '@nuxt/kit';
|
|
6
|
+
import { defineNuxtModule, createResolver, useLogger, addImports, addTemplate, addPlugin, addServerPlugin, addTypeTemplate, addComponentsDir, tryResolveModule, addRouteMiddleware, extendPages, addServerHandler, resolvePath, addComponent, hasNuxtModule } from '@nuxt/kit';
|
|
7
7
|
import { introspectionFromSchema, buildSchema } from 'graphql';
|
|
8
8
|
import { minifyIntrospection, outputIntrospectionFile } from 'gql.tada/internal';
|
|
9
9
|
import { createJiti } from 'jiti';
|
|
10
|
-
import { typeName, blockTypeName as blockTypeName$1,
|
|
11
|
-
import { migrationsDirFor } from '../dist/runtime/shared/migrations-dir.js';
|
|
12
|
-
import { entryFieldsFor, isPrivateField, blockTypeName, pageColumnFields, isTranslatableField, isTranslatableMediaField, isStringListField, pageAllFields, normalizeContentEntries, PAGE_PATH_FIELD, pageRoutes, isCollectionKind, isMultiSelect, layoutErrors, listErrors, mobileMediaKey,
|
|
10
|
+
import { typeName, blocksFieldTypeName, blockTypeName as blockTypeName$1, renderGraphqlSdl } from '../dist/runtime/shared/graphql-sdl.js';
|
|
11
|
+
import { migrationsDirFor, MIGRATION_FIXES_FILE, migrationSqlFiles, fixNewMigrations } from '../dist/runtime/shared/migrations-dir.js';
|
|
12
|
+
import { blocksFieldPaths, entryFieldsFor, isPrivateField, blockTypeName, pageColumnFields, isTranslatableField, isTranslatableMediaField, isStringListField, pageAllFields, isRequiredField, normalizeContentEntries, contentRouteConfigErrors, PAGE_PATH_FIELD, pageRoutes, isCollectionKind, isMultiSelect, layoutErrors, listErrors, mobileMediaKey, relationTarget, pageFieldsTableName, pageMediaTableName, contentRouteClashErrors, CONTENT_SYSTEM_FIELDS, CONTENT_ROUTE_FIELDS, declaredLinkTargets, isLinkTargetEntry, MAX_BLOCKS_DEPTH, CONDITION_GROUPS, normalizeTags, selectOptionValues, CONDITION_OPERATORS, routedContentNames, pageFields, isRoutedContent, mediaTypeFilter, DEFAULT_MEDIA_MAX_FILE_SIZE, normalizeCmsConfig } from '../dist/runtime/shared/index.js';
|
|
13
13
|
import { previewPathErrors, DEFAULT_CMS_PREVIEW_PATH } from '../dist/runtime/shared/preview.js';
|
|
14
|
+
import { previewBannerErrors } from '../dist/runtime/shared/preview-links.js';
|
|
15
|
+
import { seoOptionErrors, normalizeSiteUrl, SITEMAP_PATH, resolveSeoSwitches, ROBOTS_PATH, missingSiteUrlWarning, SITEMAP_CONFLICT_MODULES, ROBOTS_CONFLICT_MODULES } from '../dist/runtime/shared/seo.js';
|
|
14
16
|
import { scanMediaDirectory, readMediaFileMeta } from '../dist/runtime/server/utils/media-sync.js';
|
|
15
17
|
import { createHash } from 'node:crypto';
|
|
16
18
|
import { readFile } from 'node:fs/promises';
|
|
19
|
+
import { isUniqueValueField, UNIQUE_FIELD_TYPES, IMMUTABLE_FIELD_TYPES, DEFAULT_FIELD_TYPES, DEFAULT_NOW, DEFAULT_TODAY } from '../dist/runtime/shared/field-options.js';
|
|
17
20
|
import { routePathsFromDir, resolvePageRoutes } from '../dist/runtime/shared/page-routes.js';
|
|
21
|
+
import { CMS_ENTRY_ACTIONS, CMS_MEDIA_ACTIONS } from '../dist/runtime/shared/hooks.js';
|
|
22
|
+
import { validateRoles } from '../dist/runtime/shared/roles.js';
|
|
18
23
|
|
|
19
24
|
const DEFAULT_BRAND_TITLE = "nuxt-cms";
|
|
20
25
|
const DEFAULT_BRAND_SUBTITLE = "Content studio";
|
|
@@ -101,22 +106,42 @@ function renderBrandFile(brand) {
|
|
|
101
106
|
].join("\n");
|
|
102
107
|
}
|
|
103
108
|
|
|
109
|
+
function resolveCmsImageSetup(installed, options) {
|
|
110
|
+
const provider = options.image?.provider;
|
|
111
|
+
return {
|
|
112
|
+
nuxtImage: installed,
|
|
113
|
+
provider: installed && typeof provider === "string" && provider ? provider : null
|
|
114
|
+
};
|
|
115
|
+
}
|
|
116
|
+
function renderImageFile(setup) {
|
|
117
|
+
const lines = [`import type { Component } from 'vue'`];
|
|
118
|
+
if (setup.nuxtImage) lines.push(`import { NuxtImg, NuxtPicture } from '#components'`);
|
|
119
|
+
lines.push(
|
|
120
|
+
``,
|
|
121
|
+
`export const cmsNuxtImg: Component | null = ${setup.nuxtImage ? "NuxtImg" : "null"}`,
|
|
122
|
+
`export const cmsNuxtPicture: Component | null = ${setup.nuxtImage ? "NuxtPicture" : "null"}`,
|
|
123
|
+
`export const cmsImageProvider: string | null = ${JSON.stringify(setup.provider)}`,
|
|
124
|
+
``
|
|
125
|
+
);
|
|
126
|
+
return lines.join("\n");
|
|
127
|
+
}
|
|
128
|
+
|
|
104
129
|
function componentPascalName(name) {
|
|
105
130
|
return name.split(/[-_\s]+/).filter(Boolean).map((part) => part.charAt(0).toUpperCase() + part.slice(1)).join("");
|
|
106
131
|
}
|
|
107
132
|
function collectBlockComponents(config) {
|
|
108
133
|
const refs = [];
|
|
109
134
|
for (const [entryName, entry] of Object.entries(config)) {
|
|
110
|
-
for (const
|
|
111
|
-
if (
|
|
135
|
+
for (const { field, schemaPath } of blocksFieldPaths(entryFieldsFor(entry))) {
|
|
136
|
+
if (isPrivateField(field)) continue;
|
|
112
137
|
for (const [blockName, block] of Object.entries(field.blocks ?? {})) {
|
|
113
138
|
if (typeof block.component !== "string" || !block.component.trim()) continue;
|
|
114
139
|
refs.push({
|
|
115
140
|
entry: entryName,
|
|
116
|
-
field:
|
|
141
|
+
field: schemaPath.join("."),
|
|
117
142
|
block: blockName,
|
|
118
143
|
component: componentPascalName(block.component.trim()),
|
|
119
|
-
typeName: blockTypeName(entryName,
|
|
144
|
+
typeName: blockTypeName(entryName, schemaPath, blockName)
|
|
120
145
|
});
|
|
121
146
|
}
|
|
122
147
|
}
|
|
@@ -238,13 +263,16 @@ const RESERVED_ENTRY_KEYS = [
|
|
|
238
263
|
"graphql",
|
|
239
264
|
"settings",
|
|
240
265
|
"cms_media",
|
|
266
|
+
"cms_preview_links",
|
|
267
|
+
"cms_redirects",
|
|
241
268
|
"cms_settings",
|
|
242
269
|
"cms_users"
|
|
243
270
|
];
|
|
244
271
|
const RESERVED_COLUMNS = ["id", "status", "created_at", "updated_at"];
|
|
245
272
|
const TITLE_FIELD_TYPES = ["text", "slug", "email", "number", "date", "datetime", "select"];
|
|
246
273
|
const TRANSLATABLE_FIELD_TYPES = ["text", "richtext", "media"];
|
|
247
|
-
const
|
|
274
|
+
const TRANSLATABLE_ERROR = "translatable is only supported on text, richtext, media and link fields";
|
|
275
|
+
const CONDITION_VALUE_TYPES = [
|
|
248
276
|
"select",
|
|
249
277
|
"boolean",
|
|
250
278
|
"text",
|
|
@@ -254,12 +282,19 @@ const CONDITION_FIELD_TYPES = [
|
|
|
254
282
|
"email",
|
|
255
283
|
"slug"
|
|
256
284
|
];
|
|
285
|
+
const CONDITION_ORDER_TYPES = ["number", "date", "datetime"];
|
|
286
|
+
const CONDITION_LIST_TYPES = ["tags", "select (multiple)", "relation (many-to-many)"];
|
|
287
|
+
const DATE_PATTERN = /^\d{4}-\d{2}-\d{2}$/;
|
|
288
|
+
const DATETIME_PATTERN = /^\d{4}-\d{2}-\d{2}T\d{2}:\d{2}(?::\d{2}(?:\.\d+)?)?(?:Z|[+-]\d{2}:\d{2})$/;
|
|
257
289
|
const COMPONENT_RESERVED_KEYS = ["key", "ref", "class", "style"];
|
|
258
290
|
const RESERVED_TYPE_NAMES = [
|
|
259
291
|
"Query",
|
|
260
292
|
"Mutation",
|
|
261
293
|
"Subscription",
|
|
262
294
|
"CmsMedia",
|
|
295
|
+
"CmsLink",
|
|
296
|
+
"CmsLocalePath",
|
|
297
|
+
"CmsSite",
|
|
263
298
|
"JSON",
|
|
264
299
|
"SortDirection",
|
|
265
300
|
"IntFilter",
|
|
@@ -309,6 +344,11 @@ function validateConfig(config, i18n) {
|
|
|
309
344
|
errors.push(`${at}: kind must be 'collection', 'single', 'page' or 'content'`);
|
|
310
345
|
if (entry.kind === "content") errors.push(...contentErrors(at, entry));
|
|
311
346
|
else if (entry.preview !== void 0) errors.push(`${at}: preview needs kind 'content'`);
|
|
347
|
+
if (entry.route !== void 0) {
|
|
348
|
+
if (entry.kind === "content")
|
|
349
|
+
errors.push(...contentRouteConfigErrors(at, entry.route, { locales }));
|
|
350
|
+
else errors.push(`${at}: route needs kind 'content'`);
|
|
351
|
+
}
|
|
312
352
|
if (entry.kind === "page") {
|
|
313
353
|
if (pageEntry) {
|
|
314
354
|
errors.push(
|
|
@@ -420,13 +460,12 @@ function validateConfig(config, i18n) {
|
|
|
420
460
|
columnNames.add(column);
|
|
421
461
|
}
|
|
422
462
|
if (field.translatable) {
|
|
423
|
-
if (!TRANSLATABLE_FIELD_TYPES.includes(field.type))
|
|
424
|
-
errors.push(
|
|
425
|
-
`${fat}: translatable is only supported on text, richtext and media fields`
|
|
426
|
-
);
|
|
463
|
+
if (field.type !== "link" && !TRANSLATABLE_FIELD_TYPES.includes(field.type))
|
|
464
|
+
errors.push(`${fat}: ${TRANSLATABLE_ERROR}`);
|
|
427
465
|
if (!locales.length)
|
|
428
466
|
errors.push(`${fat}: translatable requires cms.i18n.locales in nuxt.config`);
|
|
429
467
|
}
|
|
468
|
+
errors.push(...linkFieldErrors(fat, field, config));
|
|
430
469
|
if (field.mobile) {
|
|
431
470
|
const mobileKey = mobileMediaKey(key);
|
|
432
471
|
if (field.type !== "media")
|
|
@@ -435,48 +474,10 @@ function validateConfig(config, i18n) {
|
|
|
435
474
|
errors.push(`${fat}: mobile needs the key '${mobileKey}', which is already declared`);
|
|
436
475
|
}
|
|
437
476
|
if (field.type === "select") errors.push(...selectOptionErrors(fat, field));
|
|
438
|
-
if (field.showIf) {
|
|
477
|
+
if (field.showIf !== void 0) {
|
|
439
478
|
if (entry.titleField === key)
|
|
440
479
|
errors.push(`${fat}: the titleField cannot be conditional`);
|
|
441
|
-
|
|
442
|
-
const cat = `${fat}, showIf on '${condition.field}'`;
|
|
443
|
-
const target = allFields[condition.field];
|
|
444
|
-
if (!condition.field || !target) {
|
|
445
|
-
errors.push(`${cat}: '${condition.field}' is not a declared field`);
|
|
446
|
-
continue;
|
|
447
|
-
}
|
|
448
|
-
if (condition.field === key) {
|
|
449
|
-
errors.push(`${cat}: a field cannot depend on itself`);
|
|
450
|
-
continue;
|
|
451
|
-
}
|
|
452
|
-
if (!CONDITION_FIELD_TYPES.includes(target.type) || isMultiSelect(target)) {
|
|
453
|
-
errors.push(
|
|
454
|
-
`${cat}: showIf can only depend on ${CONDITION_FIELD_TYPES.join(
|
|
455
|
-
", "
|
|
456
|
-
)} fields (got '${target.type}')`
|
|
457
|
-
);
|
|
458
|
-
continue;
|
|
459
|
-
}
|
|
460
|
-
if (isTranslatableField(target)) {
|
|
461
|
-
errors.push(`${cat}: showIf cannot depend on a translatable field`);
|
|
462
|
-
continue;
|
|
463
|
-
}
|
|
464
|
-
const values = condition.in ?? (condition.eq === void 0 ? null : [condition.eq]);
|
|
465
|
-
if (!values || !values.length) {
|
|
466
|
-
errors.push(`${cat}: showIf requires 'eq' or a non-empty 'in'`);
|
|
467
|
-
continue;
|
|
468
|
-
}
|
|
469
|
-
if (condition.in && condition.eq !== void 0)
|
|
470
|
-
errors.push(`${cat}: showIf accepts either 'eq' or 'in', not both`);
|
|
471
|
-
if (target.type === "select") {
|
|
472
|
-
const options = selectOptionValues(target);
|
|
473
|
-
const unknown = values.filter((value) => !options.includes(String(value)));
|
|
474
|
-
if (unknown.length)
|
|
475
|
-
errors.push(
|
|
476
|
-
`${cat}: ${unknown.map((value) => `'${String(value)}'`).join(", ")} not in the options of '${condition.field}'`
|
|
477
|
-
);
|
|
478
|
-
}
|
|
479
|
-
}
|
|
480
|
+
errors.push(...showIfErrors(fat, key, field.showIf, allFields, "a declared field"));
|
|
480
481
|
}
|
|
481
482
|
if (field.type === "slug" && field.from) {
|
|
482
483
|
const source = entry.fields?.[field.from];
|
|
@@ -489,59 +490,16 @@ function validateConfig(config, i18n) {
|
|
|
489
490
|
} else if (field.suggestions !== void 0) {
|
|
490
491
|
errors.push(`${fat}: suggestions is only supported on tags fields`);
|
|
491
492
|
}
|
|
492
|
-
|
|
493
|
-
|
|
493
|
+
errors.push(...valueBoundsErrors(fat, field));
|
|
494
|
+
errors.push(...fieldOptionErrors(fat, field, entry, null));
|
|
494
495
|
if (field.type === "blocks") {
|
|
495
496
|
if (field.translatable) errors.push(`${fat}: blocks fields cannot be translatable`);
|
|
496
|
-
errors.push(...
|
|
497
|
-
if (!field.blocks || !Object.keys(field.blocks).length) {
|
|
498
|
-
errors.push(`${fat}: blocks requires a non-empty blocks map`);
|
|
499
|
-
}
|
|
500
|
-
for (const [blockName, block] of Object.entries(field.blocks ?? {})) {
|
|
501
|
-
const bat = `${fat}, block '${blockName}'`;
|
|
502
|
-
if (!IDENTIFIER.test(blockName))
|
|
503
|
-
errors.push(`${bat}: key must be a valid identifier`);
|
|
504
|
-
if (!block.fields || !Object.keys(block.fields).length)
|
|
505
|
-
errors.push(`${bat}: fields must not be empty`);
|
|
506
|
-
errors.push(...blockMetaErrors(bat, block));
|
|
507
|
-
for (const [blockFieldKey, blockField] of Object.entries(block.fields ?? {})) {
|
|
508
|
-
const bfat = `${bat}, field '${blockFieldKey}'`;
|
|
509
|
-
if (!IDENTIFIER.test(blockFieldKey))
|
|
510
|
-
errors.push(`${bfat}: key must be a valid identifier`);
|
|
511
|
-
if (blockFieldKey === "type")
|
|
512
|
-
errors.push(`${bfat}: 'type' is reserved inside blocks`);
|
|
513
|
-
if (["relation", "blocks", "slug", "tags"].includes(blockField.type)) {
|
|
514
|
-
errors.push(
|
|
515
|
-
`${bfat}: ${blockField.type} fields are not supported inside blocks`
|
|
516
|
-
);
|
|
517
|
-
}
|
|
518
|
-
if (blockField.translatable) {
|
|
519
|
-
if (!TRANSLATABLE_FIELD_TYPES.includes(blockField.type))
|
|
520
|
-
errors.push(
|
|
521
|
-
`${bfat}: translatable is only supported on text, richtext and media fields`
|
|
522
|
-
);
|
|
523
|
-
if (!locales.length)
|
|
524
|
-
errors.push(
|
|
525
|
-
`${bfat}: translatable requires cms.i18n.locales in nuxt.config`
|
|
526
|
-
);
|
|
527
|
-
}
|
|
528
|
-
if (blockField.private)
|
|
529
|
-
errors.push(`${bfat}: private fields are not supported inside blocks`);
|
|
530
|
-
if (blockField.showIf)
|
|
531
|
-
errors.push(`${bfat}: showIf is not supported inside blocks`);
|
|
532
|
-
if (blockField.mobile)
|
|
533
|
-
errors.push(`${bfat}: mobile is not supported inside blocks`);
|
|
534
|
-
if (blockField.type === "select")
|
|
535
|
-
errors.push(...selectOptionErrors(bfat, blockField));
|
|
536
|
-
if (blockField.type === "select" && blockField.multiple) {
|
|
537
|
-
errors.push(`${bfat}: multiple select is not supported inside blocks`);
|
|
538
|
-
}
|
|
539
|
-
}
|
|
540
|
-
}
|
|
497
|
+
errors.push(...blocksFieldErrors(fat, field, 1, { config, entry, locales }));
|
|
541
498
|
}
|
|
542
499
|
if (field.type === "relation") {
|
|
543
|
-
const
|
|
544
|
-
|
|
500
|
+
const to = relationTarget(field);
|
|
501
|
+
const target = to ? config[to] : void 0;
|
|
502
|
+
if (!to || !target) {
|
|
545
503
|
errors.push(`${fat}: relation target '${field.to}' is not in the registry`);
|
|
546
504
|
} else {
|
|
547
505
|
if (!isCollectionKind(target))
|
|
@@ -571,6 +529,8 @@ function validateConfig(config, i18n) {
|
|
|
571
529
|
}
|
|
572
530
|
const sqlTableNames = /* @__PURE__ */ new Map([
|
|
573
531
|
["cms_media", "the built-in media table"],
|
|
532
|
+
["cms_preview_links", "the built-in preview links table"],
|
|
533
|
+
["cms_redirects", "the built-in redirects table"],
|
|
574
534
|
["cms_settings", "the built-in settings table"],
|
|
575
535
|
["cms_users", "the built-in users table"]
|
|
576
536
|
]);
|
|
@@ -596,6 +556,95 @@ function validateConfig(config, i18n) {
|
|
|
596
556
|
registerTable(`${name}_${snakeCase(key)}`, `the join table of '${name}.${key}'`);
|
|
597
557
|
}
|
|
598
558
|
}
|
|
559
|
+
errors.push(...contentRouteClashErrors(config));
|
|
560
|
+
errors.push(...blockTypeNameErrors(config));
|
|
561
|
+
return errors;
|
|
562
|
+
}
|
|
563
|
+
function blocksFieldErrors(fat, field, depth, scope) {
|
|
564
|
+
const { config, entry, locales } = scope;
|
|
565
|
+
const errors = [];
|
|
566
|
+
errors.push(...blocksCountErrors(fat, field));
|
|
567
|
+
if (!field.blocks || !Object.keys(field.blocks).length) {
|
|
568
|
+
errors.push(`${fat}: blocks requires a non-empty blocks map`);
|
|
569
|
+
}
|
|
570
|
+
for (const [blockName, block] of Object.entries(field.blocks ?? {})) {
|
|
571
|
+
const bat = `${fat}, block '${blockName}'`;
|
|
572
|
+
if (!IDENTIFIER.test(blockName)) errors.push(`${bat}: key must be a valid identifier`);
|
|
573
|
+
if (!block.fields || !Object.keys(block.fields).length)
|
|
574
|
+
errors.push(`${bat}: fields must not be empty`);
|
|
575
|
+
errors.push(...blockMetaErrors(bat, block));
|
|
576
|
+
errors.push(...layoutErrors(bat, block.fields ?? {}, block.layout));
|
|
577
|
+
for (const [blockFieldKey, blockField] of Object.entries(block.fields ?? {})) {
|
|
578
|
+
const bfat = `${bat}, field '${blockFieldKey}'`;
|
|
579
|
+
if (!IDENTIFIER.test(blockFieldKey)) errors.push(`${bfat}: key must be a valid identifier`);
|
|
580
|
+
if (blockFieldKey === "type") errors.push(`${bfat}: 'type' is reserved inside blocks`);
|
|
581
|
+
if (["relation", "slug", "tags"].includes(blockField.type)) {
|
|
582
|
+
errors.push(`${bfat}: ${blockField.type} fields are not supported inside blocks`);
|
|
583
|
+
}
|
|
584
|
+
errors.push(...linkFieldErrors(bfat, blockField, config));
|
|
585
|
+
if (blockField.translatable) {
|
|
586
|
+
if (blockField.type === "blocks")
|
|
587
|
+
errors.push(`${bfat}: blocks fields cannot be translatable`);
|
|
588
|
+
else if (blockField.type !== "link" && !TRANSLATABLE_FIELD_TYPES.includes(blockField.type))
|
|
589
|
+
errors.push(`${bfat}: ${TRANSLATABLE_ERROR}`);
|
|
590
|
+
if (!locales.length)
|
|
591
|
+
errors.push(`${bfat}: translatable requires cms.i18n.locales in nuxt.config`);
|
|
592
|
+
}
|
|
593
|
+
if (blockField.private)
|
|
594
|
+
errors.push(`${bfat}: private fields are not supported inside blocks`);
|
|
595
|
+
if (blockField.showIf !== void 0)
|
|
596
|
+
errors.push(
|
|
597
|
+
...showIfErrors(
|
|
598
|
+
bfat,
|
|
599
|
+
blockFieldKey,
|
|
600
|
+
blockField.showIf,
|
|
601
|
+
block.fields ?? {},
|
|
602
|
+
"a field of this block"
|
|
603
|
+
)
|
|
604
|
+
);
|
|
605
|
+
if (blockField.mobile)
|
|
606
|
+
errors.push(`${bfat}: mobile is not supported inside blocks`);
|
|
607
|
+
if (blockField.type === "blocks") {
|
|
608
|
+
if (depth >= MAX_BLOCKS_DEPTH)
|
|
609
|
+
errors.push(
|
|
610
|
+
`${bfat}: blocks nest at most ${MAX_BLOCKS_DEPTH} levels deep (a blocks field in a block in a blocks field counts as 2)`
|
|
611
|
+
);
|
|
612
|
+
else errors.push(...blocksFieldErrors(bfat, blockField, depth + 1, scope));
|
|
613
|
+
errors.push(...fieldOptionErrors(bfat, blockField, entry, "blocks"));
|
|
614
|
+
continue;
|
|
615
|
+
}
|
|
616
|
+
errors.push(...valueBoundsErrors(bfat, blockField));
|
|
617
|
+
errors.push(...fieldOptionErrors(bfat, blockField, entry, "blocks"));
|
|
618
|
+
if (blockField.type === "select") errors.push(...selectOptionErrors(bfat, blockField));
|
|
619
|
+
if (blockField.type === "select" && blockField.multiple) {
|
|
620
|
+
errors.push(`${bfat}: multiple select is not supported inside blocks`);
|
|
621
|
+
}
|
|
622
|
+
}
|
|
623
|
+
}
|
|
624
|
+
return errors;
|
|
625
|
+
}
|
|
626
|
+
function blockTypeNameErrors(config) {
|
|
627
|
+
const errors = [];
|
|
628
|
+
const seen = /* @__PURE__ */ new Map();
|
|
629
|
+
for (const [name, entry] of Object.entries(config)) seen.set(typeName(name), `entry '${name}'`);
|
|
630
|
+
const claim = (type, owner) => {
|
|
631
|
+
const existing = seen.get(type);
|
|
632
|
+
if (existing && existing !== owner)
|
|
633
|
+
errors.push(
|
|
634
|
+
`cms.config: GraphQL type name '${type}' of ${owner} clashes with ${existing}, rename a block or field`
|
|
635
|
+
);
|
|
636
|
+
else seen.set(type, owner);
|
|
637
|
+
};
|
|
638
|
+
for (const [name, entry] of Object.entries(config)) {
|
|
639
|
+
const fields = entry.kind === "page" ? pageAllFields(entry) : entry.fields ?? {};
|
|
640
|
+
for (const { field, schemaPath } of blocksFieldPaths(fields)) {
|
|
641
|
+
if (field.private) continue;
|
|
642
|
+
const where = `'${name}.${schemaPath.join(".")}'`;
|
|
643
|
+
claim(blocksFieldTypeName(name, schemaPath), `the blocks of ${where}`);
|
|
644
|
+
for (const blockName of Object.keys(field.blocks ?? {}))
|
|
645
|
+
claim(blockTypeName$1(name, schemaPath, blockName), `block '${blockName}' of ${where}`);
|
|
646
|
+
}
|
|
647
|
+
}
|
|
599
648
|
return errors;
|
|
600
649
|
}
|
|
601
650
|
function contentErrors(at, entry) {
|
|
@@ -604,6 +653,8 @@ function contentErrors(at, entry) {
|
|
|
604
653
|
for (const key of entry.declared?.fields ?? []) {
|
|
605
654
|
if (system.includes(key))
|
|
606
655
|
errors.push(`${at}: '${key}' is a system field of content entries, remove it from fields`);
|
|
656
|
+
else if (entry.route !== void 0 && CONTENT_ROUTE_FIELDS.includes(key))
|
|
657
|
+
errors.push(`${at}: '${key}' is reserved on content entries with a route`);
|
|
607
658
|
}
|
|
608
659
|
if (entry.declared?.titleField !== void 0)
|
|
609
660
|
errors.push(`${at}: content entries use 'title' as titleField, remove titleField`);
|
|
@@ -625,6 +676,43 @@ function contentErrors(at, entry) {
|
|
|
625
676
|
}
|
|
626
677
|
return errors;
|
|
627
678
|
}
|
|
679
|
+
function linkFieldErrors(at, field, config) {
|
|
680
|
+
const errors = [];
|
|
681
|
+
if (field.type !== "link") {
|
|
682
|
+
if (field.external !== void 0)
|
|
683
|
+
errors.push(`${at}: external is only supported on link fields`);
|
|
684
|
+
if (field.withLabel !== void 0)
|
|
685
|
+
errors.push(`${at}: withLabel is only supported on link fields`);
|
|
686
|
+
return errors;
|
|
687
|
+
}
|
|
688
|
+
if (field.external !== void 0 && typeof field.external !== "boolean")
|
|
689
|
+
errors.push(`${at}: external must be a boolean`);
|
|
690
|
+
if (field.withLabel !== void 0 && typeof field.withLabel !== "boolean")
|
|
691
|
+
errors.push(`${at}: withLabel must be a boolean`);
|
|
692
|
+
if (field.translatable && !field.withLabel)
|
|
693
|
+
errors.push(`${at}: translatable needs withLabel (only the link text is translated)`);
|
|
694
|
+
const to = field.to;
|
|
695
|
+
const declared = declaredLinkTargets(field);
|
|
696
|
+
if (to !== void 0 && (!declared || declared.length !== to.length)) {
|
|
697
|
+
errors.push(`${at}: to must be a list of entry names`);
|
|
698
|
+
return errors;
|
|
699
|
+
}
|
|
700
|
+
const seen = /* @__PURE__ */ new Set();
|
|
701
|
+
for (const name of declared ?? []) {
|
|
702
|
+
if (seen.has(name)) errors.push(`${at}: link target '${name}' is listed twice`);
|
|
703
|
+
seen.add(name);
|
|
704
|
+
const target = config[name];
|
|
705
|
+
if (!target) errors.push(`${at}: link target '${name}' is not in the registry`);
|
|
706
|
+
else if (!isLinkTargetEntry(target))
|
|
707
|
+
errors.push(
|
|
708
|
+
`${at}: link target '${name}' must be the page entry or a content entry with a route`
|
|
709
|
+
);
|
|
710
|
+
}
|
|
711
|
+
const targets = declared ?? Object.keys(config).filter((name) => isLinkTargetEntry(config[name]));
|
|
712
|
+
if (field.external === false && !targets.length)
|
|
713
|
+
errors.push(`${at}: the link allows no target: list entries in 'to' or allow external URLs`);
|
|
714
|
+
return errors;
|
|
715
|
+
}
|
|
628
716
|
function selectOptionErrors(at, field) {
|
|
629
717
|
const errors = [];
|
|
630
718
|
if (!Array.isArray(field.options) || !field.options.length)
|
|
@@ -664,6 +752,282 @@ function blocksCountErrors(at, field) {
|
|
|
664
752
|
errors.push(`${at}: min (${field.min}) cannot be greater than max (${field.max})`);
|
|
665
753
|
return errors;
|
|
666
754
|
}
|
|
755
|
+
function conditionTypeName(field) {
|
|
756
|
+
if (isMultiSelect(field)) return "select (multiple)";
|
|
757
|
+
if (field.type === "relation") return `relation (${field.cardinality ?? "many-to-one"})`;
|
|
758
|
+
return field.type;
|
|
759
|
+
}
|
|
760
|
+
function isConditionListTarget(field) {
|
|
761
|
+
return isStringListField(field) || isManyToMany(field);
|
|
762
|
+
}
|
|
763
|
+
function isDateValue(value) {
|
|
764
|
+
return typeof value === "string" && DATE_PATTERN.test(value) && !Number.isNaN((/* @__PURE__ */ new Date(`${value}T00:00:00Z`)).getTime());
|
|
765
|
+
}
|
|
766
|
+
function isDateTimeValue(value) {
|
|
767
|
+
return typeof value === "string" && DATETIME_PATTERN.test(value) && !Number.isNaN(new Date(value).getTime());
|
|
768
|
+
}
|
|
769
|
+
function conditionValueExpectation(field) {
|
|
770
|
+
switch (field.type) {
|
|
771
|
+
case "boolean":
|
|
772
|
+
return { label: "a boolean", test: (value) => typeof value === "boolean" };
|
|
773
|
+
case "number":
|
|
774
|
+
return {
|
|
775
|
+
label: "a number",
|
|
776
|
+
test: (value) => typeof value === "number" && Number.isFinite(value)
|
|
777
|
+
};
|
|
778
|
+
case "date":
|
|
779
|
+
return { label: "a yyyy-mm-dd date", test: isDateValue };
|
|
780
|
+
case "datetime":
|
|
781
|
+
return {
|
|
782
|
+
label: "an ISO 8601 date and time with a time zone",
|
|
783
|
+
test: isDateTimeValue
|
|
784
|
+
};
|
|
785
|
+
default:
|
|
786
|
+
return { label: "a string", test: (value) => typeof value === "string" };
|
|
787
|
+
}
|
|
788
|
+
}
|
|
789
|
+
function conditionOptionErrors(at, name, target, values) {
|
|
790
|
+
const options = selectOptionValues(target);
|
|
791
|
+
const unknown = values.filter((value) => !options.includes(String(value)));
|
|
792
|
+
if (!unknown.length) return [];
|
|
793
|
+
return [
|
|
794
|
+
`${at}: ${unknown.map((value) => `'${String(value)}'`).join(", ")} not in the options of '${name}'`
|
|
795
|
+
];
|
|
796
|
+
}
|
|
797
|
+
const ORDER_OPERATORS = ["gt", "gte", "lt", "lte"];
|
|
798
|
+
function conditionLeafErrors(at, key, condition, scope, scopeLabel) {
|
|
799
|
+
const name = condition.field;
|
|
800
|
+
const cat = `${at}, showIf on '${String(name)}'`;
|
|
801
|
+
if (typeof name !== "string" || !name || !Object.hasOwn(scope, name))
|
|
802
|
+
return [`${cat}: '${String(name)}' is not ${scopeLabel}`];
|
|
803
|
+
if (name === key) return [`${cat}: a field cannot depend on itself`];
|
|
804
|
+
const errors = [];
|
|
805
|
+
const target = scope[name];
|
|
806
|
+
const operators = CONDITION_OPERATORS;
|
|
807
|
+
const unknown = Object.keys(condition).filter((k) => k !== "field" && !operators.includes(k));
|
|
808
|
+
if (unknown.length)
|
|
809
|
+
errors.push(`${cat}: unknown showIf key ${unknown.map((k) => `'${k}'`).join(", ")}`);
|
|
810
|
+
const used = CONDITION_OPERATORS.filter((operator) => condition[operator] !== void 0);
|
|
811
|
+
if (!used.length) {
|
|
812
|
+
errors.push(`${cat}: showIf needs an operator (${CONDITION_OPERATORS.join(", ")})`);
|
|
813
|
+
return errors;
|
|
814
|
+
}
|
|
815
|
+
const type = conditionTypeName(target);
|
|
816
|
+
for (const operator of used) {
|
|
817
|
+
const value = condition[operator];
|
|
818
|
+
if (operator === "empty") {
|
|
819
|
+
if (target.type === "boolean")
|
|
820
|
+
errors.push(`${cat}: 'empty' does not apply to boolean fields, use 'eq'`);
|
|
821
|
+
else if (typeof value !== "boolean") errors.push(`${cat}: 'empty' must be true or false`);
|
|
822
|
+
continue;
|
|
823
|
+
}
|
|
824
|
+
if (isTranslatableField(target)) {
|
|
825
|
+
errors.push(
|
|
826
|
+
`${cat}: showIf cannot depend on a translatable field with '${operator}', only with 'empty'`
|
|
827
|
+
);
|
|
828
|
+
continue;
|
|
829
|
+
}
|
|
830
|
+
if (operator === "has") {
|
|
831
|
+
if (!isConditionListTarget(target))
|
|
832
|
+
errors.push(
|
|
833
|
+
`${cat}: showIf can only depend on ${CONDITION_LIST_TYPES.join(
|
|
834
|
+
", "
|
|
835
|
+
)} fields with 'has' (got '${type}')`
|
|
836
|
+
);
|
|
837
|
+
else if (typeof value !== "string" || !value)
|
|
838
|
+
errors.push(`${cat}: 'has' needs a non-empty string`);
|
|
839
|
+
else if (target.type === "select")
|
|
840
|
+
errors.push(...conditionOptionErrors(cat, name, target, [value]));
|
|
841
|
+
continue;
|
|
842
|
+
}
|
|
843
|
+
const allowed = ORDER_OPERATORS.includes(operator) ? CONDITION_ORDER_TYPES : CONDITION_VALUE_TYPES;
|
|
844
|
+
if (!allowed.includes(target.type) || isMultiSelect(target)) {
|
|
845
|
+
errors.push(
|
|
846
|
+
`${cat}: showIf can only depend on ${allowed.join(
|
|
847
|
+
", "
|
|
848
|
+
)} fields with '${operator}' (got '${type}')`
|
|
849
|
+
);
|
|
850
|
+
continue;
|
|
851
|
+
}
|
|
852
|
+
const isList = operator === "in" || operator === "notIn";
|
|
853
|
+
if (isList && (!Array.isArray(value) || !value.length)) {
|
|
854
|
+
errors.push(`${cat}: '${operator}' needs a non-empty list`);
|
|
855
|
+
continue;
|
|
856
|
+
}
|
|
857
|
+
const values = isList ? value : [value];
|
|
858
|
+
const expectation = conditionValueExpectation(target);
|
|
859
|
+
const wrong = values.filter((item) => !expectation.test(item));
|
|
860
|
+
if (wrong.length)
|
|
861
|
+
errors.push(
|
|
862
|
+
`${cat}: '${operator}' needs ${expectation.label} for a ${type} field (got ${wrong.map((item) => JSON.stringify(item)).join(", ")})`
|
|
863
|
+
);
|
|
864
|
+
else if (target.type === "select")
|
|
865
|
+
errors.push(...conditionOptionErrors(cat, name, target, values));
|
|
866
|
+
}
|
|
867
|
+
return errors;
|
|
868
|
+
}
|
|
869
|
+
function showIfErrors(at, key, showIf, scope, scopeLabel) {
|
|
870
|
+
const errors = [];
|
|
871
|
+
const visit = (node) => {
|
|
872
|
+
if (Array.isArray(node)) {
|
|
873
|
+
if (!node.length) errors.push(`${at}: showIf needs at least one condition`);
|
|
874
|
+
for (const child of node) visit(child);
|
|
875
|
+
return;
|
|
876
|
+
}
|
|
877
|
+
if (!node || typeof node !== "object") {
|
|
878
|
+
errors.push(`${at}: every showIf condition must be an object`);
|
|
879
|
+
return;
|
|
880
|
+
}
|
|
881
|
+
const record = node;
|
|
882
|
+
const group = CONDITION_GROUPS.find((name) => Object.hasOwn(record, name));
|
|
883
|
+
if (!group) {
|
|
884
|
+
errors.push(...conditionLeafErrors(at, key, record, scope, scopeLabel));
|
|
885
|
+
return;
|
|
886
|
+
}
|
|
887
|
+
if (Object.keys(record).length !== 1) {
|
|
888
|
+
errors.push(`${at}: a showIf group holds only one of 'all', 'any' or 'not'`);
|
|
889
|
+
return;
|
|
890
|
+
}
|
|
891
|
+
const inner = record[group];
|
|
892
|
+
if (group === "not") {
|
|
893
|
+
if (!inner || typeof inner !== "object" || Array.isArray(inner))
|
|
894
|
+
errors.push(`${at}: showIf 'not' takes one condition`);
|
|
895
|
+
else visit(inner);
|
|
896
|
+
return;
|
|
897
|
+
}
|
|
898
|
+
if (!Array.isArray(inner) || !inner.length) {
|
|
899
|
+
errors.push(`${at}: showIf '${group}' takes a non-empty list of conditions`);
|
|
900
|
+
return;
|
|
901
|
+
}
|
|
902
|
+
for (const child of inner) visit(child);
|
|
903
|
+
};
|
|
904
|
+
visit(showIf);
|
|
905
|
+
return errors;
|
|
906
|
+
}
|
|
907
|
+
const EMAIL_PATTERN = /^[^\s@]+@[^\s@]+\.[^\s@]+$/;
|
|
908
|
+
function valueBoundsErrors(at, field) {
|
|
909
|
+
if (field.type === "blocks" || field.type === "tags") return [];
|
|
910
|
+
const errors = [];
|
|
911
|
+
const raw = field;
|
|
912
|
+
for (const name of ["minLength", "maxLength"])
|
|
913
|
+
if (raw[name] !== void 0)
|
|
914
|
+
errors.push(
|
|
915
|
+
`${at}: ${name} is not an option, use ${name.slice(0, 3)} (characters on text fields)`
|
|
916
|
+
);
|
|
917
|
+
if (field.min === void 0 && field.max === void 0) return errors;
|
|
918
|
+
if (field.type === "number") {
|
|
919
|
+
const isBound = (value) => typeof value === "number" && Number.isFinite(value);
|
|
920
|
+
if (field.min !== void 0 && !isBound(field.min)) errors.push(`${at}: min must be a number`);
|
|
921
|
+
if (field.max !== void 0 && !isBound(field.max)) errors.push(`${at}: max must be a number`);
|
|
922
|
+
if (isBound(field.min) && isBound(field.max) && field.min > field.max)
|
|
923
|
+
errors.push(`${at}: min (${field.min}) cannot be greater than max (${field.max})`);
|
|
924
|
+
return errors;
|
|
925
|
+
}
|
|
926
|
+
if (field.type === "text") {
|
|
927
|
+
const isLength = (value, least) => typeof value === "number" && Number.isInteger(value) && value >= least;
|
|
928
|
+
if (field.min !== void 0 && !isLength(field.min, 0))
|
|
929
|
+
errors.push(`${at}: min must be an integer of 0 or more (characters)`);
|
|
930
|
+
if (field.max !== void 0 && !isLength(field.max, 1))
|
|
931
|
+
errors.push(`${at}: max must be an integer of 1 or more (characters)`);
|
|
932
|
+
if (isLength(field.min, 0) && isLength(field.max, 1) && field.min > field.max)
|
|
933
|
+
errors.push(`${at}: min (${field.min}) cannot be greater than max (${field.max})`);
|
|
934
|
+
return errors;
|
|
935
|
+
}
|
|
936
|
+
errors.push(`${at}: min and max are only supported on number, text, blocks and tags fields`);
|
|
937
|
+
return errors;
|
|
938
|
+
}
|
|
939
|
+
function defaultValueError(field) {
|
|
940
|
+
const value = field.default;
|
|
941
|
+
switch (field.type) {
|
|
942
|
+
case "text":
|
|
943
|
+
case "richtext": {
|
|
944
|
+
if (typeof value !== "string") return "default must be a string";
|
|
945
|
+
if (field.type === "text" && value.trim()) {
|
|
946
|
+
if (field.min !== void 0 && value.length < field.min)
|
|
947
|
+
return `default is shorter than min (${field.min})`;
|
|
948
|
+
if (field.max !== void 0 && value.length > field.max)
|
|
949
|
+
return `default is longer than max (${field.max})`;
|
|
950
|
+
}
|
|
951
|
+
return null;
|
|
952
|
+
}
|
|
953
|
+
case "number":
|
|
954
|
+
if (typeof value !== "number" || !Number.isFinite(value)) return "default must be a number";
|
|
955
|
+
if (field.integer && !Number.isInteger(value)) return "default must be an integer";
|
|
956
|
+
if (field.min !== void 0 && value < field.min)
|
|
957
|
+
return `default is lower than min (${field.min})`;
|
|
958
|
+
if (field.max !== void 0 && value > field.max)
|
|
959
|
+
return `default is greater than max (${field.max})`;
|
|
960
|
+
return null;
|
|
961
|
+
case "boolean":
|
|
962
|
+
return typeof value === "boolean" ? null : "default must be true or false";
|
|
963
|
+
case "date":
|
|
964
|
+
return value === DEFAULT_TODAY || isDateValue(value) ? null : `default must be '${DEFAULT_TODAY}' or a yyyy-mm-dd date`;
|
|
965
|
+
case "datetime":
|
|
966
|
+
return value === DEFAULT_NOW || isDateTimeValue(value) ? null : `default must be '${DEFAULT_NOW}' or an ISO 8601 date and time with a time zone`;
|
|
967
|
+
case "email":
|
|
968
|
+
return typeof value === "string" && EMAIL_PATTERN.test(value) ? null : "default must be a valid email";
|
|
969
|
+
case "select": {
|
|
970
|
+
const options = selectOptionValues(field);
|
|
971
|
+
if (!field.multiple)
|
|
972
|
+
return typeof value === "string" && options.includes(value) ? null : `default must be one of the options (${options.join(", ")})`;
|
|
973
|
+
if (!Array.isArray(value)) return "default of a multiple select must be a list of options";
|
|
974
|
+
const unknown = value.filter((item) => typeof item !== "string" || !options.includes(item));
|
|
975
|
+
if (unknown.length)
|
|
976
|
+
return `default holds values that are not options: ${unknown.map((item) => JSON.stringify(item)).join(", ")}`;
|
|
977
|
+
return new Set(value).size === value.length ? null : "default repeats an option";
|
|
978
|
+
}
|
|
979
|
+
case "tags": {
|
|
980
|
+
if (!Array.isArray(value) || value.some((item) => typeof item !== "string" || !item.trim()))
|
|
981
|
+
return "default must be a list of non-empty strings";
|
|
982
|
+
const count = normalizeTags(value).length;
|
|
983
|
+
return field.max !== void 0 && count > field.max ? `default holds more than max (${field.max}) tags` : null;
|
|
984
|
+
}
|
|
985
|
+
default:
|
|
986
|
+
return `default is only supported on ${DEFAULT_FIELD_TYPES.join(", ")} fields`;
|
|
987
|
+
}
|
|
988
|
+
}
|
|
989
|
+
function fieldOptionErrors(at, field, entry, scope) {
|
|
990
|
+
const errors = [];
|
|
991
|
+
const place = scope === "blocks" ? "inside blocks" : entry.kind === "page" ? "on pages" : null;
|
|
992
|
+
if (field.default !== void 0) {
|
|
993
|
+
if (place) errors.push(`${at}: default is not supported ${place}`);
|
|
994
|
+
else {
|
|
995
|
+
const problem = defaultValueError(field);
|
|
996
|
+
if (problem) errors.push(`${at}: ${problem}`);
|
|
997
|
+
}
|
|
998
|
+
}
|
|
999
|
+
if (field.unique !== void 0 && typeof field.unique !== "boolean")
|
|
1000
|
+
errors.push(`${at}: unique must be a boolean`);
|
|
1001
|
+
if (field.unique === true) {
|
|
1002
|
+
if (!UNIQUE_FIELD_TYPES.includes(field.type))
|
|
1003
|
+
errors.push(
|
|
1004
|
+
`${at}: unique is only supported on ${UNIQUE_FIELD_TYPES.join(", ")} fields${field.type === "slug" ? " (a slug is always unique)" : ""}`
|
|
1005
|
+
);
|
|
1006
|
+
else if (place) errors.push(`${at}: unique is not supported ${place}`);
|
|
1007
|
+
else if (entry.kind === "single")
|
|
1008
|
+
errors.push(`${at}: unique is not supported on singles, which hold one item`);
|
|
1009
|
+
else if (field.translatable)
|
|
1010
|
+
errors.push(`${at}: unique is not supported on translatable fields`);
|
|
1011
|
+
else if (field.default !== void 0)
|
|
1012
|
+
errors.push(
|
|
1013
|
+
`${at}: default is not supported on unique fields (every new item would get the same value)`
|
|
1014
|
+
);
|
|
1015
|
+
}
|
|
1016
|
+
if (field.immutable !== void 0 && typeof field.immutable !== "boolean")
|
|
1017
|
+
errors.push(`${at}: immutable must be a boolean`);
|
|
1018
|
+
if (field.immutable === true) {
|
|
1019
|
+
if (!IMMUTABLE_FIELD_TYPES.includes(field.type) || isManyToMany(field))
|
|
1020
|
+
errors.push(
|
|
1021
|
+
`${at}: immutable is only supported on ${IMMUTABLE_FIELD_TYPES.join(
|
|
1022
|
+
", "
|
|
1023
|
+
)} fields (not many-to-many)`
|
|
1024
|
+
);
|
|
1025
|
+
else if (place) errors.push(`${at}: immutable is not supported ${place}`);
|
|
1026
|
+
else if (field.translatable)
|
|
1027
|
+
errors.push(`${at}: immutable is not supported on translatable fields`);
|
|
1028
|
+
}
|
|
1029
|
+
return errors;
|
|
1030
|
+
}
|
|
667
1031
|
function tagsOptionErrors(at, field) {
|
|
668
1032
|
const errors = [];
|
|
669
1033
|
if (field.min !== void 0) errors.push(`${at}: min is not supported on tags fields`);
|
|
@@ -691,6 +1055,10 @@ function blockMetaErrors(at, block) {
|
|
|
691
1055
|
errors.push(`${at}: icon must be a non-empty string`);
|
|
692
1056
|
if (block.description !== void 0 && typeof block.description !== "string")
|
|
693
1057
|
errors.push(`${at}: description must be a string`);
|
|
1058
|
+
if (block.group !== void 0 && (typeof block.group !== "string" || !block.group.trim()))
|
|
1059
|
+
errors.push(`${at}: group must be a non-empty string`);
|
|
1060
|
+
if (block.tags !== void 0 && !(Array.isArray(block.tags) && block.tags.every((tag) => typeof tag === "string" && !!tag.trim())))
|
|
1061
|
+
errors.push(`${at}: tags must be an array of non-empty strings`);
|
|
694
1062
|
return errors;
|
|
695
1063
|
}
|
|
696
1064
|
function pageColumnsErrors(at, entry) {
|
|
@@ -722,6 +1090,13 @@ function pageColumnsErrors(at, entry) {
|
|
|
722
1090
|
}
|
|
723
1091
|
return errors;
|
|
724
1092
|
}
|
|
1093
|
+
function uniqueTextColumns(config) {
|
|
1094
|
+
return Object.entries(config).filter(([, entry]) => !entry.table && entry.kind !== "page").flatMap(
|
|
1095
|
+
([name, entry]) => Object.entries(entry.fields).filter(
|
|
1096
|
+
([, field]) => isUniqueValueField(field) && field.type !== "number" && !isRequiredField(field)
|
|
1097
|
+
).map(([key]) => ({ table: name, column: snakeCase(key) }))
|
|
1098
|
+
);
|
|
1099
|
+
}
|
|
725
1100
|
function snakeCase(key) {
|
|
726
1101
|
return key.replace(/[A-Z]/g, (c) => `_${c.toLowerCase()}`);
|
|
727
1102
|
}
|
|
@@ -748,6 +1123,7 @@ function columnExpr(key, field, dialect, table) {
|
|
|
748
1123
|
break;
|
|
749
1124
|
case "json":
|
|
750
1125
|
case "blocks":
|
|
1126
|
+
case "link":
|
|
751
1127
|
expr = jsonExpr(col, dialect);
|
|
752
1128
|
break;
|
|
753
1129
|
case "select":
|
|
@@ -768,6 +1144,7 @@ function columnExpr(key, field, dialect, table) {
|
|
|
768
1144
|
default:
|
|
769
1145
|
expr = `text('${col}')`;
|
|
770
1146
|
}
|
|
1147
|
+
if (isUniqueValueField(field)) expr += ".unique()";
|
|
771
1148
|
if (isRequiredField(field)) expr += ".notNull()";
|
|
772
1149
|
return ` ${key}: ${expr},`;
|
|
773
1150
|
}
|
|
@@ -863,6 +1240,35 @@ function usersTableExpr(dialect) {
|
|
|
863
1240
|
updatedAt: ${nowExpr(pg, "updated_at")},
|
|
864
1241
|
})`;
|
|
865
1242
|
}
|
|
1243
|
+
function redirectsTableExpr(dialect) {
|
|
1244
|
+
const pg = dialect === "postgres";
|
|
1245
|
+
const tableFn = pg ? "pgTable" : "sqliteTable";
|
|
1246
|
+
return `export const cms_redirects = ${tableFn}('cms_redirects', {
|
|
1247
|
+
id: text('id').primaryKey(),
|
|
1248
|
+
from: text('from_path').notNull().unique(),
|
|
1249
|
+
to: text('to_path').notNull(),
|
|
1250
|
+
status: integer('status').notNull().default(301),
|
|
1251
|
+
keepQuery: ${pg ? `boolean('keep_query')` : `integer('keep_query', { mode: 'boolean' })`}.notNull().default(true),
|
|
1252
|
+
source: text('source').notNull().default('manual'),
|
|
1253
|
+
createdAt: ${nowExpr(pg, "created_at")},
|
|
1254
|
+
updatedAt: ${nowExpr(pg, "updated_at")},
|
|
1255
|
+
})`;
|
|
1256
|
+
}
|
|
1257
|
+
function previewLinksTableExpr(dialect) {
|
|
1258
|
+
const pg = dialect === "postgres";
|
|
1259
|
+
const tableFn = pg ? "pgTable" : "sqliteTable";
|
|
1260
|
+
return `export const cms_preview_links = ${tableFn}('cms_preview_links', {
|
|
1261
|
+
id: text('id').primaryKey(),
|
|
1262
|
+
tokenHash: text('token_hash').notNull().unique(),
|
|
1263
|
+
entry: text('entry').notNull(),
|
|
1264
|
+
itemId: text('item_id').notNull(),
|
|
1265
|
+
locale: text('locale'),
|
|
1266
|
+
createdBy: text('created_by'),
|
|
1267
|
+
createdAt: ${nowExpr(pg, "created_at")},
|
|
1268
|
+
expiresAt: text('expires_at').notNull(),
|
|
1269
|
+
revokedAt: text('revoked_at'),
|
|
1270
|
+
})`;
|
|
1271
|
+
}
|
|
866
1272
|
function mediaTableExpr(dialect) {
|
|
867
1273
|
const pg = dialect === "postgres";
|
|
868
1274
|
const tableFn = pg ? "pgTable" : "sqliteTable";
|
|
@@ -875,6 +1281,8 @@ function mediaTableExpr(dialect) {
|
|
|
875
1281
|
size: integer('size'),
|
|
876
1282
|
width: integer('width'),
|
|
877
1283
|
height: integer('height'),
|
|
1284
|
+
focalX: ${pg ? `doublePrecision('focal_x')` : `real('focal_x')`},
|
|
1285
|
+
focalY: ${pg ? `doublePrecision('focal_y')` : `real('focal_y')`},
|
|
878
1286
|
createdAt: ${nowExpr(pg, "created_at")},
|
|
879
1287
|
})`;
|
|
880
1288
|
}
|
|
@@ -886,12 +1294,11 @@ function renderSchemaFile(config, dialect, resolveImport = (s) => s) {
|
|
|
886
1294
|
const rowsEntries = derived.filter(([, entry]) => entry.kind === "page");
|
|
887
1295
|
const pg = dialect === "postgres";
|
|
888
1296
|
const core = /* @__PURE__ */ new Set(["integer", "text", pg ? "pgTable" : "sqliteTable"]);
|
|
889
|
-
|
|
890
|
-
core.add(pg ? "doublePrecision" : "real");
|
|
1297
|
+
core.add(pg ? "doublePrecision" : "real");
|
|
891
1298
|
if (pg && fields.some((f) => f.type === "date")) core.add("date");
|
|
892
1299
|
if (pg) core.add("boolean");
|
|
893
1300
|
if (pg && fields.some(
|
|
894
|
-
(f) => f.type === "json" || f.type === "blocks" || isTranslatableField(f) && !isTranslatableMediaField(f) || isStringListField(f)
|
|
1301
|
+
(f) => f.type === "json" || f.type === "blocks" || f.type === "link" || isTranslatableField(f) && !isTranslatableMediaField(f) || isStringListField(f)
|
|
895
1302
|
))
|
|
896
1303
|
core.add("jsonb");
|
|
897
1304
|
if (pg) core.add("timestamp");
|
|
@@ -919,6 +1326,8 @@ ${[
|
|
|
919
1326
|
mediaTableExpr(dialect),
|
|
920
1327
|
settingsTableExpr(dialect),
|
|
921
1328
|
usersTableExpr(dialect),
|
|
1329
|
+
redirectsTableExpr(dialect),
|
|
1330
|
+
previewLinksTableExpr(dialect),
|
|
922
1331
|
...tables,
|
|
923
1332
|
...joins,
|
|
924
1333
|
...rows
|
|
@@ -926,38 +1335,47 @@ ${[
|
|
|
926
1335
|
`;
|
|
927
1336
|
}
|
|
928
1337
|
|
|
929
|
-
const MEDIA_SELECTION = "key url type alt folder mime size width height";
|
|
930
|
-
|
|
1338
|
+
const MEDIA_SELECTION = "key url type alt folder mime size width height focalX focalY";
|
|
1339
|
+
const LINK_SELECTION = "href label newTab external entry id";
|
|
1340
|
+
function fieldSelection(key, field) {
|
|
1341
|
+
if (field.type === "media") return `${key} { ${MEDIA_SELECTION} }`;
|
|
1342
|
+
if (field.type === "link") return `${key} { ${LINK_SELECTION} }`;
|
|
1343
|
+
return key;
|
|
1344
|
+
}
|
|
1345
|
+
function blocksSelection(entryName, schemaPath, field) {
|
|
931
1346
|
const parts = ["__typename type"];
|
|
932
1347
|
for (const [blockName, block] of Object.entries(field.blocks ?? {})) {
|
|
933
1348
|
const fields = Object.entries(block.fields).map(
|
|
934
|
-
([blockFieldKey, blockField]) => blockField.type === "
|
|
1349
|
+
([blockFieldKey, blockField]) => blockField.type === "blocks" ? `${blockFieldKey} { ${blocksSelection(
|
|
1350
|
+
entryName,
|
|
1351
|
+
[...schemaPath, blockName, blockFieldKey],
|
|
1352
|
+
blockField
|
|
1353
|
+
)} }` : fieldSelection(blockFieldKey, blockField)
|
|
935
1354
|
);
|
|
936
1355
|
if (!fields.length) continue;
|
|
937
|
-
parts.push(
|
|
1356
|
+
parts.push(
|
|
1357
|
+
`... on ${blockTypeName$1(entryName, schemaPath, blockName)} { ${fields.join(" ")} }`
|
|
1358
|
+
);
|
|
938
1359
|
}
|
|
939
1360
|
return parts.join(" ");
|
|
940
1361
|
}
|
|
941
1362
|
function entrySelection(config, name, entry, withRelations, fields = entryFieldsFor(entry)) {
|
|
942
1363
|
const parts = ["id"];
|
|
943
1364
|
if (entry.kind === "page") parts.push("path");
|
|
1365
|
+
if (isRoutedContent(entry)) parts.push("path", "paths { locale path }");
|
|
944
1366
|
for (const [key, field] of Object.entries(fields)) {
|
|
945
1367
|
if (isPrivateField(field)) continue;
|
|
946
1368
|
if (field.type === "relation") {
|
|
947
|
-
const target = config[field
|
|
1369
|
+
const target = config[relationTarget(field)];
|
|
948
1370
|
if (!withRelations || !target) continue;
|
|
949
|
-
parts.push(`${key} { ${entrySelection(config, field
|
|
950
|
-
continue;
|
|
951
|
-
}
|
|
952
|
-
if (field.type === "media") {
|
|
953
|
-
parts.push(`${key} { ${MEDIA_SELECTION} }`);
|
|
1371
|
+
parts.push(`${key} { ${entrySelection(config, relationTarget(field), target, false)} }`);
|
|
954
1372
|
continue;
|
|
955
1373
|
}
|
|
956
1374
|
if (field.type === "blocks") {
|
|
957
|
-
parts.push(`${key} { ${blocksSelection(name, key, field)} }`);
|
|
1375
|
+
parts.push(`${key} { ${blocksSelection(name, [key], field)} }`);
|
|
958
1376
|
continue;
|
|
959
1377
|
}
|
|
960
|
-
parts.push(key);
|
|
1378
|
+
parts.push(fieldSelection(key, field));
|
|
961
1379
|
}
|
|
962
1380
|
if (isCollectionKind(entry)) parts.push("createdAt");
|
|
963
1381
|
parts.push("updatedAt");
|
|
@@ -986,6 +1404,19 @@ function contentItemQuery(config, name, entry) {
|
|
|
986
1404
|
const selection = entrySelection(config, name, entry, true);
|
|
987
1405
|
return `query CmsContent($slug: String!, $locale: String) { item: ${name}BySlug(slug: $slug, locale: $locale) { ${selection} } }`;
|
|
988
1406
|
}
|
|
1407
|
+
function contentRouteQuery(config) {
|
|
1408
|
+
const names = routedContentNames(config);
|
|
1409
|
+
if (!names.length) return null;
|
|
1410
|
+
const fields = names.map(
|
|
1411
|
+
(name) => `${name}: ${name}ByPath(path: $path, locale: $locale) { ${entrySelection(
|
|
1412
|
+
config,
|
|
1413
|
+
name,
|
|
1414
|
+
config[name],
|
|
1415
|
+
true
|
|
1416
|
+
)} }`
|
|
1417
|
+
);
|
|
1418
|
+
return `query CmsRoute($path: String!, $locale: String) { ${fields.join(" ")} }`;
|
|
1419
|
+
}
|
|
989
1420
|
function pageQuery(config, name, entry, path) {
|
|
990
1421
|
const selection = entrySelection(config, name, entry, true, pageFields(entry, path));
|
|
991
1422
|
return `query CmsPage($path: String!, $locale: String) { page: ${name}ByPath(path: $path, locale: $locale) { ${selection} } }`;
|
|
@@ -1042,6 +1473,10 @@ function renderQueriesFile(config) {
|
|
|
1042
1473
|
`export const cmsContentItemQueries: Record<string, string> = {`,
|
|
1043
1474
|
...contentItems,
|
|
1044
1475
|
`}`,
|
|
1476
|
+
``,
|
|
1477
|
+
`export const cmsRouteEntries: string[] = ${JSON.stringify(routedContentNames(config))}`,
|
|
1478
|
+
``,
|
|
1479
|
+
`export const cmsRouteQuery: string | null = ${JSON.stringify(contentRouteQuery(config))}`,
|
|
1045
1480
|
``
|
|
1046
1481
|
].join("\n");
|
|
1047
1482
|
}
|
|
@@ -1066,11 +1501,12 @@ function scalarTsType(field) {
|
|
|
1066
1501
|
}
|
|
1067
1502
|
function fieldTsType(config, entryName, key, field) {
|
|
1068
1503
|
if (field.type === "relation") {
|
|
1069
|
-
const target = typeName(field
|
|
1504
|
+
const target = typeName(relationTarget(field));
|
|
1070
1505
|
if (field.cardinality === "many-to-many") return `${target}[]`;
|
|
1071
|
-
return isRequiredField(field) && !config[field
|
|
1506
|
+
return isRequiredField(field) && !config[relationTarget(field)]?.drafts ? target : `${target} | null`;
|
|
1072
1507
|
}
|
|
1073
1508
|
if (field.type === "media") return `${mediaTsType(field)} | null`;
|
|
1509
|
+
if (field.type === "link") return "CmsLink | null";
|
|
1074
1510
|
if (field.type === "tags") return "string[]";
|
|
1075
1511
|
if (field.type === "select" && field.multiple) {
|
|
1076
1512
|
const union = selectOptionValues(field).map((o) => JSON.stringify(o)).join(" | ");
|
|
@@ -1084,20 +1520,32 @@ function fieldTsType(config, entryName, key, field) {
|
|
|
1084
1520
|
const base = scalarTsType(field);
|
|
1085
1521
|
return isRequiredField(field) ? base : `${base} | null`;
|
|
1086
1522
|
}
|
|
1087
|
-
function blockPropsTypeName(entryName,
|
|
1088
|
-
return `${blockTypeName$1(entryName,
|
|
1523
|
+
function blockPropsTypeName(entryName, schemaPath, blockName) {
|
|
1524
|
+
return `${blockTypeName$1(entryName, schemaPath, blockName)}Props`;
|
|
1525
|
+
}
|
|
1526
|
+
function blockFieldTs(entryName, schemaPath, blockName, key, field) {
|
|
1527
|
+
if (field.type === "media") return mediaTsType(field);
|
|
1528
|
+
if (field.type === "link") return "CmsLink";
|
|
1529
|
+
if (field.type === "blocks")
|
|
1530
|
+
return `${blocksFieldTypeName(entryName, [...schemaPath, blockName, key])}[]`;
|
|
1531
|
+
return scalarTsType(field);
|
|
1089
1532
|
}
|
|
1090
|
-
function blockTypesTs(entryName,
|
|
1533
|
+
function blockTypesTs(entryName, schemaPath, field) {
|
|
1091
1534
|
const defs = [];
|
|
1535
|
+
const nested = [];
|
|
1092
1536
|
const members = [];
|
|
1093
1537
|
for (const [blockName, block] of Object.entries(field.blocks ?? {})) {
|
|
1094
|
-
const name = blockTypeName$1(entryName,
|
|
1095
|
-
const props = blockPropsTypeName(entryName,
|
|
1538
|
+
const name = blockTypeName$1(entryName, schemaPath, blockName);
|
|
1539
|
+
const props = blockPropsTypeName(entryName, schemaPath, blockName);
|
|
1096
1540
|
members.push(name);
|
|
1097
1541
|
const lines = [];
|
|
1098
1542
|
for (const [blockFieldKey, blockField] of Object.entries(block.fields)) {
|
|
1099
|
-
const base =
|
|
1543
|
+
const base = blockFieldTs(entryName, schemaPath, blockName, blockFieldKey, blockField);
|
|
1100
1544
|
lines.push(` ${blockFieldKey}: ${base} | null`);
|
|
1545
|
+
if (blockField.type === "blocks")
|
|
1546
|
+
nested.push(
|
|
1547
|
+
...blockTypesTs(entryName, [...schemaPath, blockName, blockFieldKey], blockField)
|
|
1548
|
+
);
|
|
1101
1549
|
}
|
|
1102
1550
|
defs.push(`export interface ${props} {
|
|
1103
1551
|
${lines.join("\n")}
|
|
@@ -1110,12 +1558,15 @@ ${lines.join("\n")}
|
|
|
1110
1558
|
);
|
|
1111
1559
|
}
|
|
1112
1560
|
if (members.length)
|
|
1113
|
-
defs.push(
|
|
1114
|
-
|
|
1561
|
+
defs.push(
|
|
1562
|
+
`export type ${blocksFieldTypeName(entryName, schemaPath)} = ${members.join(" | ")}`
|
|
1563
|
+
);
|
|
1564
|
+
return [...defs, ...nested];
|
|
1115
1565
|
}
|
|
1116
1566
|
function entryTs(config, name, entry) {
|
|
1117
1567
|
const lines = [" id: string"];
|
|
1118
1568
|
if (entry.kind === "page") lines.push(" path: string");
|
|
1569
|
+
if (isRoutedContent(entry)) lines.push(" path: string", " paths: CmsLocalePath[]");
|
|
1119
1570
|
for (const [key, field] of Object.entries(entryFieldsFor(entry))) {
|
|
1120
1571
|
if (isPrivateField(field)) continue;
|
|
1121
1572
|
lines.push(` ${key}: ${fieldTsType(config, name, key, field)}`);
|
|
@@ -1144,9 +1595,9 @@ function autoTypeTs(config, name, entry) {
|
|
|
1144
1595
|
if (!keys.length) return `export type ${auto} = ${typeName(name)}`;
|
|
1145
1596
|
const lines = keys.map((key) => {
|
|
1146
1597
|
const field = entry.fields[key];
|
|
1147
|
-
const shallow = shallowTypeTs(config, field
|
|
1598
|
+
const shallow = shallowTypeTs(config, relationTarget(field));
|
|
1148
1599
|
if (field.cardinality === "many-to-many") return ` ${key}: ${shallow}[]`;
|
|
1149
|
-
const nonNull = isRequiredField(field) && !config[field
|
|
1600
|
+
const nonNull = isRequiredField(field) && !config[relationTarget(field)]?.drafts;
|
|
1150
1601
|
return ` ${key}: ${shallow}${nonNull ? "" : " | null"}`;
|
|
1151
1602
|
});
|
|
1152
1603
|
return `export type ${auto} = Omit<${typeName(name)}, ${quotedKeys(keys)}> & {
|
|
@@ -1185,6 +1636,7 @@ function entryMapsTs(config) {
|
|
|
1185
1636
|
else if (entry.kind === "content") contents.push(line);
|
|
1186
1637
|
else collections.push(line);
|
|
1187
1638
|
}
|
|
1639
|
+
const routed = routedContentNames(config);
|
|
1188
1640
|
if (!pages.length) {
|
|
1189
1641
|
pages.push(
|
|
1190
1642
|
`export interface CmsPageTypes {
|
|
@@ -1206,6 +1658,12 @@ ${contents.join("\n")}
|
|
|
1206
1658
|
`export type CmsSingleName = keyof CmsSingleTypes`,
|
|
1207
1659
|
`export type CmsCollectionName = keyof CmsCollectionTypes`,
|
|
1208
1660
|
`export type CmsContentName = keyof CmsContentTypes`,
|
|
1661
|
+
`export type CmsRoutedContentName = ${routed.length ? quotedKeys(routed) : "never"}`,
|
|
1662
|
+
[
|
|
1663
|
+
`export type CmsContentRouteMatch = {`,
|
|
1664
|
+
` [K in CmsRoutedContentName]: { entry: K; item: CmsContentTypes[K] }`,
|
|
1665
|
+
`}[CmsRoutedContentName]`
|
|
1666
|
+
].join("\n"),
|
|
1209
1667
|
...pages
|
|
1210
1668
|
];
|
|
1211
1669
|
}
|
|
@@ -1213,15 +1671,21 @@ function blockPropsMapTs(config) {
|
|
|
1213
1671
|
const entries = [];
|
|
1214
1672
|
for (const [name, entry] of Object.entries(config)) {
|
|
1215
1673
|
const fields = [];
|
|
1216
|
-
for (const
|
|
1217
|
-
if (
|
|
1674
|
+
for (const { field, schemaPath } of blocksFieldPaths(entryFieldsFor(entry))) {
|
|
1675
|
+
if (isPrivateField(field)) continue;
|
|
1218
1676
|
const blocks = Object.keys(field.blocks ?? {}).map(
|
|
1219
|
-
(blockName) => ` ${JSON.stringify(blockName)}: ${blockPropsTypeName(
|
|
1677
|
+
(blockName) => ` ${JSON.stringify(blockName)}: ${blockPropsTypeName(
|
|
1678
|
+
name,
|
|
1679
|
+
schemaPath,
|
|
1680
|
+
blockName
|
|
1681
|
+
)}`
|
|
1220
1682
|
);
|
|
1221
1683
|
if (blocks.length)
|
|
1222
|
-
fields.push(
|
|
1684
|
+
fields.push(
|
|
1685
|
+
` ${JSON.stringify(schemaPath.join("."))}: {
|
|
1223
1686
|
${blocks.join("\n")}
|
|
1224
|
-
}`
|
|
1687
|
+
}`
|
|
1688
|
+
);
|
|
1225
1689
|
}
|
|
1226
1690
|
if (fields.length) entries.push(` ${JSON.stringify(name)}: {
|
|
1227
1691
|
${fields.join("\n")}
|
|
@@ -1245,7 +1709,19 @@ ${entries.join("\n")}
|
|
|
1245
1709
|
}
|
|
1246
1710
|
function renderTypesFile(config) {
|
|
1247
1711
|
const parts = [
|
|
1712
|
+
`export interface CmsLocalePath {
|
|
1713
|
+
locale: string
|
|
1714
|
+
path: string
|
|
1715
|
+
}`,
|
|
1248
1716
|
`export type CmsMediaType = 'image' | 'video' | 'file'`,
|
|
1717
|
+
`export interface CmsLink {
|
|
1718
|
+
href: string | null
|
|
1719
|
+
label: string | null
|
|
1720
|
+
newTab: boolean
|
|
1721
|
+
external: boolean
|
|
1722
|
+
entry: string | null
|
|
1723
|
+
id: string | null
|
|
1724
|
+
}`,
|
|
1249
1725
|
`export interface CmsMedia<T extends CmsMediaType = CmsMediaType> {
|
|
1250
1726
|
key: string
|
|
1251
1727
|
url: string | null
|
|
@@ -1256,12 +1732,14 @@ function renderTypesFile(config) {
|
|
|
1256
1732
|
size: number | null
|
|
1257
1733
|
width: number | null
|
|
1258
1734
|
height: number | null
|
|
1735
|
+
focalX: number | null
|
|
1736
|
+
focalY: number | null
|
|
1259
1737
|
}`
|
|
1260
1738
|
];
|
|
1261
1739
|
for (const [name, entry] of Object.entries(config)) {
|
|
1262
1740
|
for (const [key, field] of Object.entries(entryFieldsFor(entry))) {
|
|
1263
1741
|
if (field.type === "blocks" && !isPrivateField(field))
|
|
1264
|
-
parts.push(...blockTypesTs(name, key, field));
|
|
1742
|
+
parts.push(...blockTypesTs(name, [key], field));
|
|
1265
1743
|
}
|
|
1266
1744
|
parts.push(entryTs(config, name, entry));
|
|
1267
1745
|
}
|
|
@@ -1282,6 +1760,107 @@ function resolveCmsEnabled(explicit, envValue) {
|
|
|
1282
1760
|
return true;
|
|
1283
1761
|
}
|
|
1284
1762
|
|
|
1763
|
+
const DEFAULT_WEBHOOK_TIMEOUT = 5e3;
|
|
1764
|
+
const MAX_WEBHOOK_TIMEOUT = 3e4;
|
|
1765
|
+
const MIN_WEBHOOK_SECRET_LENGTH = 16;
|
|
1766
|
+
const WEBHOOK_KEYS = /* @__PURE__ */ new Set(["name", "url", "secret", "events", "timeout"]);
|
|
1767
|
+
const WEBHOOK_NAME_PATTERN = /^[a-z]+(?:[A-Z][a-z]+)*[0-9]*$/;
|
|
1768
|
+
const WEBHOOK_EVENT_FILTERS = [
|
|
1769
|
+
"entry",
|
|
1770
|
+
"media",
|
|
1771
|
+
...CMS_ENTRY_ACTIONS.map((action) => `entry:${action}`),
|
|
1772
|
+
...CMS_MEDIA_ACTIONS.map((action) => `media:${action}`)
|
|
1773
|
+
];
|
|
1774
|
+
function webhookEnvName(name, key) {
|
|
1775
|
+
return `NUXT_CMS_WEBHOOKS_${name.replace(/([a-z])([A-Z])/g, "$1_$2").toUpperCase()}_${key.toUpperCase()}`;
|
|
1776
|
+
}
|
|
1777
|
+
function isRecord(value) {
|
|
1778
|
+
return !!value && typeof value === "object" && !Array.isArray(value);
|
|
1779
|
+
}
|
|
1780
|
+
function urlError(label, url) {
|
|
1781
|
+
try {
|
|
1782
|
+
const parsed = new URL(url);
|
|
1783
|
+
if (parsed.protocol === "http:" || parsed.protocol === "https:") return null;
|
|
1784
|
+
} catch {
|
|
1785
|
+
return `${label}.url '${url}' is not a valid URL`;
|
|
1786
|
+
}
|
|
1787
|
+
return `${label}.url '${url}' must use http or https`;
|
|
1788
|
+
}
|
|
1789
|
+
function resolveWebhooks(input, env = {}) {
|
|
1790
|
+
const webhooks = {};
|
|
1791
|
+
const errors = [];
|
|
1792
|
+
const warnings = [];
|
|
1793
|
+
if (input === void 0) return { webhooks, errors, warnings };
|
|
1794
|
+
if (!Array.isArray(input)) {
|
|
1795
|
+
return {
|
|
1796
|
+
webhooks,
|
|
1797
|
+
errors: ["webhooks must be an array of { name, url, secret, events }"],
|
|
1798
|
+
warnings
|
|
1799
|
+
};
|
|
1800
|
+
}
|
|
1801
|
+
input.forEach((item, index) => {
|
|
1802
|
+
const label = `webhooks[${index}]`;
|
|
1803
|
+
if (!isRecord(item)) {
|
|
1804
|
+
errors.push(`${label} must be an object`);
|
|
1805
|
+
return;
|
|
1806
|
+
}
|
|
1807
|
+
for (const key of Object.keys(item)) {
|
|
1808
|
+
if (!WEBHOOK_KEYS.has(key)) errors.push(`${label} has an unknown key '${key}'`);
|
|
1809
|
+
}
|
|
1810
|
+
const { name, url, secret, events, timeout } = item;
|
|
1811
|
+
if (typeof name !== "string" || !WEBHOOK_NAME_PATTERN.test(name)) {
|
|
1812
|
+
errors.push(
|
|
1813
|
+
`${label}.name must be a camelCase identifier like 'deploy' or 'searchIndex', got ${JSON.stringify(
|
|
1814
|
+
name
|
|
1815
|
+
)}`
|
|
1816
|
+
);
|
|
1817
|
+
return;
|
|
1818
|
+
}
|
|
1819
|
+
const named = `webhooks '${name}'`;
|
|
1820
|
+
if (Object.hasOwn(webhooks, name)) {
|
|
1821
|
+
errors.push(`${named} is declared twice`);
|
|
1822
|
+
return;
|
|
1823
|
+
}
|
|
1824
|
+
if (url !== void 0 && typeof url !== "string") errors.push(`${named}.url must be a string`);
|
|
1825
|
+
else if (url) {
|
|
1826
|
+
const error = urlError(named, url);
|
|
1827
|
+
if (error) errors.push(error);
|
|
1828
|
+
} else if (!env[webhookEnvName(name, "url")]) {
|
|
1829
|
+
warnings.push(
|
|
1830
|
+
`${named} has no url: set ${webhookEnvName(name, "url")} at runtime or it never fires`
|
|
1831
|
+
);
|
|
1832
|
+
}
|
|
1833
|
+
if (secret !== void 0 && typeof secret !== "string")
|
|
1834
|
+
errors.push(`${named}.secret must be a string`);
|
|
1835
|
+
else if (secret && secret.length < MIN_WEBHOOK_SECRET_LENGTH)
|
|
1836
|
+
errors.push(`${named}.secret must be at least ${MIN_WEBHOOK_SECRET_LENGTH} characters`);
|
|
1837
|
+
if (events !== void 0) {
|
|
1838
|
+
if (!Array.isArray(events) || !events.length) {
|
|
1839
|
+
errors.push(`${named}.events must be a non-empty array (omit it for every event)`);
|
|
1840
|
+
} else {
|
|
1841
|
+
for (const value of events) {
|
|
1842
|
+
if (!WEBHOOK_EVENT_FILTERS.includes(value))
|
|
1843
|
+
errors.push(
|
|
1844
|
+
`${named}.events has an unknown event ${JSON.stringify(
|
|
1845
|
+
value
|
|
1846
|
+
)}: use 'entry', 'media', 'entry:<action>' or 'media:<action>'`
|
|
1847
|
+
);
|
|
1848
|
+
}
|
|
1849
|
+
}
|
|
1850
|
+
}
|
|
1851
|
+
if (timeout !== void 0 && (typeof timeout !== "number" || !Number.isInteger(timeout) || timeout < 1 || timeout > MAX_WEBHOOK_TIMEOUT)) {
|
|
1852
|
+
errors.push(`${named}.timeout must be an integer between 1 and ${MAX_WEBHOOK_TIMEOUT} ms`);
|
|
1853
|
+
}
|
|
1854
|
+
webhooks[name] = {
|
|
1855
|
+
url: typeof url === "string" ? url : "",
|
|
1856
|
+
secret: typeof secret === "string" ? secret : "",
|
|
1857
|
+
events: Array.isArray(events) ? [...new Set(events)] : [],
|
|
1858
|
+
timeout: typeof timeout === "number" ? timeout : DEFAULT_WEBHOOK_TIMEOUT
|
|
1859
|
+
};
|
|
1860
|
+
});
|
|
1861
|
+
return { webhooks, errors, warnings };
|
|
1862
|
+
}
|
|
1863
|
+
|
|
1285
1864
|
function resolveDatabaseOptions(database) {
|
|
1286
1865
|
const shared = {
|
|
1287
1866
|
migrateOnBoot: database?.migrateOnBoot ?? true,
|
|
@@ -1312,6 +1891,9 @@ function resolveDatabaseOptions(database) {
|
|
|
1312
1891
|
}
|
|
1313
1892
|
return { ...shared, driver: "sqlite", path: database?.path ?? "data/cms.db" };
|
|
1314
1893
|
}
|
|
1894
|
+
const MODULE_VERSION = JSON.parse(
|
|
1895
|
+
readFileSync(new URL("../package.json", import.meta.url), "utf8")
|
|
1896
|
+
).version;
|
|
1315
1897
|
const DEFAULT_FILESYSTEM_MEDIA_DIR = "data/media";
|
|
1316
1898
|
const DEFAULT_FILESYSTEM_MEDIA_BASE_URL = "/media";
|
|
1317
1899
|
function resolveMediaOptions(media) {
|
|
@@ -1377,11 +1959,69 @@ function resolveModuleOptions(options) {
|
|
|
1377
1959
|
},
|
|
1378
1960
|
preview: {
|
|
1379
1961
|
path: options.preview?.path ?? DEFAULT_CMS_PREVIEW_PATH,
|
|
1380
|
-
component: options.preview?.component?.trim() ?? ""
|
|
1962
|
+
component: options.preview?.component?.trim() ?? "",
|
|
1963
|
+
banner: options.preview?.banner ?? true
|
|
1381
1964
|
}
|
|
1382
1965
|
};
|
|
1383
1966
|
}
|
|
1384
1967
|
const moduleRequire = createRequire(import.meta.url);
|
|
1968
|
+
function resolveSiteUrlOption(nuxt, options) {
|
|
1969
|
+
const existing = nuxt.options.runtimeConfig.public.siteUrl;
|
|
1970
|
+
const nuxtSite = nuxt.options.site?.url;
|
|
1971
|
+
const candidates = [
|
|
1972
|
+
options.site?.url,
|
|
1973
|
+
typeof existing === "string" ? existing : void 0,
|
|
1974
|
+
typeof nuxtSite === "string" ? nuxtSite : void 0,
|
|
1975
|
+
process.env.NUXT_PUBLIC_SITE_URL
|
|
1976
|
+
];
|
|
1977
|
+
return normalizeSiteUrl(candidates.find((candidate) => candidate?.trim()));
|
|
1978
|
+
}
|
|
1979
|
+
function setupSiteUrl(nuxt, options, logger) {
|
|
1980
|
+
const errors = seoOptionErrors(options.seo, options.site);
|
|
1981
|
+
if (errors.length) throw new Error(`[nuxt-cms] ${errors.join("\n")}`);
|
|
1982
|
+
const existing = nuxt.options.runtimeConfig.public.siteUrl;
|
|
1983
|
+
const siteUrl = resolveSiteUrlOption(nuxt, options);
|
|
1984
|
+
if (options.site?.url && typeof existing === "string" && existing.trim() && normalizeSiteUrl(existing) !== siteUrl) {
|
|
1985
|
+
logger.warn(
|
|
1986
|
+
`[nuxt-cms] cms.site.url (${siteUrl}) replaces runtimeConfig.public.siteUrl (${existing}).`
|
|
1987
|
+
);
|
|
1988
|
+
}
|
|
1989
|
+
nuxt.options.runtimeConfig.public.siteUrl = siteUrl;
|
|
1990
|
+
nuxt.options.runtimeConfig.public.cmsTrustForwardedHost = options.site?.trustForwardedHost === true;
|
|
1991
|
+
}
|
|
1992
|
+
function setupSeoRoutes(nuxt, resolver, seo, publicDir, logger) {
|
|
1993
|
+
nuxt.hook("modules:done", () => {
|
|
1994
|
+
const installed = (names) => names.filter((name) => hasNuxtModule(name, nuxt));
|
|
1995
|
+
const publicFile = (file) => existsSync(join(publicDir, file)) ? [`public/${file}`] : [];
|
|
1996
|
+
const sitemapConflicts = [
|
|
1997
|
+
...installed(SITEMAP_CONFLICT_MODULES),
|
|
1998
|
+
...publicFile(SITEMAP_PATH.slice(1))
|
|
1999
|
+
];
|
|
2000
|
+
const switches = resolveSeoSwitches(seo, {
|
|
2001
|
+
sitemap: sitemapConflicts,
|
|
2002
|
+
robots: [...installed(ROBOTS_CONFLICT_MODULES), ...publicFile(ROBOTS_PATH.slice(1))]
|
|
2003
|
+
});
|
|
2004
|
+
for (const warning of switches.warnings) logger.warn(warning);
|
|
2005
|
+
const siteUrlWarning = nuxt.options.runtimeConfig.public.siteUrl ? null : missingSiteUrlWarning(switches);
|
|
2006
|
+
if (siteUrlWarning && !nuxt.options.dev) logger.warn(siteUrlWarning);
|
|
2007
|
+
const runtime = nuxt.options.runtimeConfig.cms;
|
|
2008
|
+
runtime.seo = {
|
|
2009
|
+
sitemap: switches.sitemap,
|
|
2010
|
+
robots: switches.robots,
|
|
2011
|
+
robotsSitemap: switches.sitemap || sitemapConflicts.length > 0
|
|
2012
|
+
};
|
|
2013
|
+
if (switches.sitemap)
|
|
2014
|
+
addServerHandler({
|
|
2015
|
+
route: SITEMAP_PATH,
|
|
2016
|
+
handler: resolver.resolve("./runtime/server/routes/sitemap")
|
|
2017
|
+
});
|
|
2018
|
+
if (switches.robots)
|
|
2019
|
+
addServerHandler({
|
|
2020
|
+
route: ROBOTS_PATH,
|
|
2021
|
+
handler: resolver.resolve("./runtime/server/routes/robots")
|
|
2022
|
+
});
|
|
2023
|
+
});
|
|
2024
|
+
}
|
|
1385
2025
|
function resolveImport(specifier) {
|
|
1386
2026
|
try {
|
|
1387
2027
|
return fileURLToPath(import.meta.resolve(specifier)).replace(/\\/g, "/");
|
|
@@ -1528,6 +2168,30 @@ function addCmsBlocks(nuxt, resolver, cmsConfig, previewComponent) {
|
|
|
1528
2168
|
name: "CmsBlocks",
|
|
1529
2169
|
filePath: resolver.resolve("./runtime/app/blocks/CmsBlocks.vue")
|
|
1530
2170
|
});
|
|
2171
|
+
addComponent({
|
|
2172
|
+
name: "CmsLink",
|
|
2173
|
+
filePath: resolver.resolve("./runtime/app/links/CmsLink.vue")
|
|
2174
|
+
});
|
|
2175
|
+
addComponent({
|
|
2176
|
+
name: "CmsField",
|
|
2177
|
+
filePath: resolver.resolve("./runtime/app/blocks/CmsField")
|
|
2178
|
+
});
|
|
2179
|
+
addPlugin(resolver.resolve("./runtime/app/plugins/cms-field"));
|
|
2180
|
+
addTypeTemplate({
|
|
2181
|
+
filename: "types/nuxt-cms-field.d.ts",
|
|
2182
|
+
getContents: () => [
|
|
2183
|
+
"import type { Directive } from 'vue'",
|
|
2184
|
+
"",
|
|
2185
|
+
"declare module 'vue' {",
|
|
2186
|
+
" interface GlobalDirectives {",
|
|
2187
|
+
" vCmsField: Directive<HTMLElement, string>",
|
|
2188
|
+
" }",
|
|
2189
|
+
"}",
|
|
2190
|
+
"",
|
|
2191
|
+
"export {}",
|
|
2192
|
+
""
|
|
2193
|
+
].join("\n")
|
|
2194
|
+
});
|
|
1531
2195
|
if (!refs.length && !previews.length) return;
|
|
1532
2196
|
nuxt.hook("components:extend", (components) => {
|
|
1533
2197
|
const names = components.map((component) => component.pascalName);
|
|
@@ -1540,6 +2204,23 @@ function addCmsBlocks(nuxt, resolver, cmsConfig, previewComponent) {
|
|
|
1540
2204
|
${errors.join("\n")}`);
|
|
1541
2205
|
});
|
|
1542
2206
|
}
|
|
2207
|
+
function addCmsImage(nuxt, resolver) {
|
|
2208
|
+
const imageTemplate = addTemplate({
|
|
2209
|
+
filename: "cms/image.ts",
|
|
2210
|
+
write: true,
|
|
2211
|
+
getContents: () => renderImageFile(
|
|
2212
|
+
resolveCmsImageSetup(
|
|
2213
|
+
hasNuxtModule("@nuxt/image", nuxt),
|
|
2214
|
+
nuxt.options
|
|
2215
|
+
)
|
|
2216
|
+
)
|
|
2217
|
+
});
|
|
2218
|
+
nuxt.options.alias["#cms-image"] = imageTemplate.dst;
|
|
2219
|
+
addComponent({
|
|
2220
|
+
name: "CmsImage",
|
|
2221
|
+
filePath: resolver.resolve("./runtime/app/image/CmsImage.vue")
|
|
2222
|
+
});
|
|
2223
|
+
}
|
|
1543
2224
|
const module$1 = defineNuxtModule({
|
|
1544
2225
|
meta: {
|
|
1545
2226
|
name: "@xleddyl/nuxt-cms",
|
|
@@ -1602,6 +2283,13 @@ const module$1 = defineNuxtModule({
|
|
|
1602
2283
|
const resolver = createResolver(import.meta.url);
|
|
1603
2284
|
const logger = useLogger("nuxt-cms");
|
|
1604
2285
|
const resolved = resolveModuleOptions(options);
|
|
2286
|
+
const webhooks = resolveWebhooks(options.webhooks, process.env);
|
|
2287
|
+
if (webhooks.errors.length) {
|
|
2288
|
+
throw new Error(`[nuxt-cms] Invalid webhooks:
|
|
2289
|
+
${webhooks.errors.join("\n")}`);
|
|
2290
|
+
}
|
|
2291
|
+
for (const warning of webhooks.warnings) logger.warn(`[nuxt-cms] ${warning}`);
|
|
2292
|
+
setupSiteUrl(nuxt, options, logger);
|
|
1605
2293
|
if (!resolveCmsEnabled(options.enabled, process.env[CMS_ENABLED_ENV])) {
|
|
1606
2294
|
const queryStub = resolver.resolve("./runtime/app/composables/cms-query-disabled");
|
|
1607
2295
|
const entryStub = resolver.resolve("./runtime/app/composables/cms-entry-disabled");
|
|
@@ -1612,7 +2300,17 @@ const module$1 = defineNuxtModule({
|
|
|
1612
2300
|
{ name: "useCmsCollection", from: entryStub },
|
|
1613
2301
|
{ name: "useCmsPage", from: entryStub },
|
|
1614
2302
|
{ name: "useCmsContents", from: entryStub },
|
|
1615
|
-
{ name: "useCmsContent", from: entryStub }
|
|
2303
|
+
{ name: "useCmsContent", from: entryStub },
|
|
2304
|
+
{ name: "useCmsRoute", from: entryStub },
|
|
2305
|
+
{
|
|
2306
|
+
name: "useCmsSite",
|
|
2307
|
+
from: resolver.resolve("./runtime/app/composables/cms-site-disabled")
|
|
2308
|
+
},
|
|
2309
|
+
{ name: "useCmsSeo", from: resolver.resolve("./runtime/app/composables/cms-seo") },
|
|
2310
|
+
{
|
|
2311
|
+
name: "useCmsPreviewMode",
|
|
2312
|
+
from: resolver.resolve("./runtime/app/composables/cms-preview-mode")
|
|
2313
|
+
}
|
|
1616
2314
|
]);
|
|
1617
2315
|
const disabledConfig = await loadCmsConfig(
|
|
1618
2316
|
nuxt,
|
|
@@ -1623,12 +2321,14 @@ const module$1 = defineNuxtModule({
|
|
|
1623
2321
|
);
|
|
1624
2322
|
addCmsTypeTemplates(nuxt, disabledConfig);
|
|
1625
2323
|
addCmsBlocks(nuxt, resolver, disabledConfig, resolved.preview.component);
|
|
2324
|
+
addCmsImage(nuxt, resolver);
|
|
1626
2325
|
nuxt.options.runtimeConfig.public.cms = {
|
|
1627
2326
|
mediaBaseUrl: resolved.media.publicBaseUrl,
|
|
1628
2327
|
mediaStorage: resolved.media.storage,
|
|
1629
2328
|
mediaMaxFileSize: resolved.media.maxFileSize,
|
|
1630
2329
|
i18n: resolved.i18n,
|
|
1631
|
-
previewPath: resolved.preview.path
|
|
2330
|
+
previewPath: resolved.preview.path,
|
|
2331
|
+
version: MODULE_VERSION
|
|
1632
2332
|
};
|
|
1633
2333
|
logger.info(
|
|
1634
2334
|
"[nuxt-cms] disabled: registering no-op query composables and generated types, skipping admin, server and database setup"
|
|
@@ -1674,19 +2374,61 @@ const module$1 = defineNuxtModule({
|
|
|
1674
2374
|
});
|
|
1675
2375
|
nuxt.options.alias["#cms-tables"] = schemaTemplate.dst;
|
|
1676
2376
|
addCmsTypeTemplates(nuxt, cmsConfig);
|
|
2377
|
+
const roles = validateRoles(options.roles, cmsConfig);
|
|
2378
|
+
if (roles.errors.length) {
|
|
2379
|
+
for (const error of roles.errors) logger.error(error);
|
|
2380
|
+
throw new Error(
|
|
2381
|
+
`[nuxt-cms] Invalid cms.roles (${roles.errors.length} error${roles.errors.length > 1 ? "s" : ""})`
|
|
2382
|
+
);
|
|
2383
|
+
}
|
|
2384
|
+
for (const warning of roles.warnings) logger.warn(`[nuxt-cms] ${warning}`);
|
|
2385
|
+
const rolesTypes = resolver.resolve("./runtime/shared/roles").replace(/\\/g, "/");
|
|
2386
|
+
addTemplate({
|
|
2387
|
+
filename: "cms/roles.d.ts",
|
|
2388
|
+
write: true,
|
|
2389
|
+
getContents: () => [
|
|
2390
|
+
`import type { CmsRoles } from '${rolesTypes}'`,
|
|
2391
|
+
``,
|
|
2392
|
+
`declare const roles: CmsRoles`,
|
|
2393
|
+
``,
|
|
2394
|
+
`export default roles`,
|
|
2395
|
+
``
|
|
2396
|
+
].join("\n")
|
|
2397
|
+
});
|
|
2398
|
+
const rolesTemplate = addTemplate({
|
|
2399
|
+
filename: "cms/roles.js",
|
|
2400
|
+
write: true,
|
|
2401
|
+
getContents: () => `export default ${JSON.stringify(roles.roles, null, 3)}
|
|
2402
|
+
`
|
|
2403
|
+
});
|
|
2404
|
+
nuxt.options.alias["#cms-roles"] = rolesTemplate.dst;
|
|
1677
2405
|
const queryComposables = resolver.resolve("./runtime/app/composables/cms-query");
|
|
1678
2406
|
const entryComposables = resolver.resolve("./runtime/app/composables/cms-entry");
|
|
1679
2407
|
addImports([
|
|
2408
|
+
{ name: "useCmsSite", from: resolver.resolve("./runtime/app/composables/cms-site") },
|
|
2409
|
+
{ name: "useCmsSeo", from: resolver.resolve("./runtime/app/composables/cms-seo") },
|
|
1680
2410
|
{ name: "useCms", from: queryComposables },
|
|
1681
2411
|
{ name: "$cmsQuery", from: queryComposables },
|
|
1682
2412
|
{ name: "useCmsSingle", from: entryComposables },
|
|
1683
2413
|
{ name: "useCmsCollection", from: entryComposables },
|
|
1684
2414
|
{ name: "useCmsPage", from: entryComposables },
|
|
1685
2415
|
{ name: "useCmsContents", from: entryComposables },
|
|
1686
|
-
{ name: "useCmsContent", from: entryComposables }
|
|
2416
|
+
{ name: "useCmsContent", from: entryComposables },
|
|
2417
|
+
{ name: "useCmsRoute", from: entryComposables },
|
|
2418
|
+
{
|
|
2419
|
+
name: "useCmsPreviewMode",
|
|
2420
|
+
from: resolver.resolve("./runtime/app/composables/cms-preview-mode")
|
|
2421
|
+
}
|
|
1687
2422
|
]);
|
|
1688
2423
|
addCmsBlocks(nuxt, resolver, cmsConfig, resolved.preview.component);
|
|
1689
|
-
|
|
2424
|
+
addCmsImage(nuxt, resolver);
|
|
2425
|
+
addPlugin(resolver.resolve("./runtime/app/plugins/cms-preview"));
|
|
2426
|
+
if (resolved.preview.banner)
|
|
2427
|
+
addPlugin(resolver.resolve("./runtime/app/plugins/cms-preview-banner.client"));
|
|
2428
|
+
const previewErrors = [
|
|
2429
|
+
...previewPathErrors(resolved.preview.path, Object.keys(cmsConfig)),
|
|
2430
|
+
...previewBannerErrors(options.preview?.banner)
|
|
2431
|
+
];
|
|
1690
2432
|
if (previewErrors.length) throw new Error(`[nuxt-cms] ${previewErrors.join("\n")}`);
|
|
1691
2433
|
const previewPath = resolved.preview.path;
|
|
1692
2434
|
const previewRule = nuxt.options.routeRules?.[previewPath] ?? {};
|
|
@@ -1729,6 +2471,16 @@ const module$1 = defineNuxtModule({
|
|
|
1729
2471
|
const relativeSchemaPath = relative(nuxt.options.rootDir, schemaTemplate.dst);
|
|
1730
2472
|
const relativeMigrationsDir = relative(nuxt.options.rootDir, migrationsDir);
|
|
1731
2473
|
const toPosix = (path) => path.replace(/\\/g, "/");
|
|
2474
|
+
const migrationFixes = {
|
|
2475
|
+
dialect,
|
|
2476
|
+
out: toPosix(migrationsDir),
|
|
2477
|
+
uniqueTextColumns: uniqueTextColumns(cmsConfig)
|
|
2478
|
+
};
|
|
2479
|
+
addTemplate({
|
|
2480
|
+
filename: `cms/${MIGRATION_FIXES_FILE}`,
|
|
2481
|
+
write: true,
|
|
2482
|
+
getContents: () => JSON.stringify(migrationFixes, null, 2)
|
|
2483
|
+
});
|
|
1732
2484
|
addTemplate({
|
|
1733
2485
|
filename: "cms/drizzle.config.ts",
|
|
1734
2486
|
write: true,
|
|
@@ -1803,6 +2555,9 @@ const module$1 = defineNuxtModule({
|
|
|
1803
2555
|
)
|
|
1804
2556
|
);
|
|
1805
2557
|
addServerPlugin(resolver.resolve("./runtime/server/plugins/session-validate"));
|
|
2558
|
+
if (Object.keys(webhooks.webhooks).length) {
|
|
2559
|
+
addServerPlugin(resolver.resolve("./runtime/server/plugins/webhooks"));
|
|
2560
|
+
}
|
|
1806
2561
|
if (!nuxt.options.dev) {
|
|
1807
2562
|
addServerPlugin(resolver.resolve("./runtime/server/plugins/session-check"));
|
|
1808
2563
|
}
|
|
@@ -1819,11 +2574,22 @@ const module$1 = defineNuxtModule({
|
|
|
1819
2574
|
`--schema=${relativeSchemaPath}`,
|
|
1820
2575
|
`--out=${relativeMigrationsDir}`
|
|
1821
2576
|
];
|
|
2577
|
+
const before = migrationSqlFiles(migrationsDir);
|
|
1822
2578
|
const code = await new Promise((done) => {
|
|
1823
2579
|
spawn(process.execPath, args, { cwd: nuxt.options.rootDir, stdio: "inherit" }).on("close", (exitCode) => done(exitCode ?? 1)).on("error", () => done(1));
|
|
1824
2580
|
});
|
|
1825
|
-
if (code !== 0)
|
|
2581
|
+
if (code !== 0) {
|
|
1826
2582
|
logger.error(`[nuxt-cms] drizzle-kit generate failed (exit code ${code})`);
|
|
2583
|
+
return;
|
|
2584
|
+
}
|
|
2585
|
+
const fixed = fixNewMigrations(
|
|
2586
|
+
migrationsDir,
|
|
2587
|
+
before,
|
|
2588
|
+
migrationFixes.uniqueTextColumns,
|
|
2589
|
+
dialect
|
|
2590
|
+
);
|
|
2591
|
+
for (const file of fixed)
|
|
2592
|
+
logger.info(`[nuxt-cms] ${file}: blank values of new unique fields become NULL`);
|
|
1827
2593
|
});
|
|
1828
2594
|
}
|
|
1829
2595
|
const mediaFilesystemBase = resolved.media.storage === "filesystem" ? `/${resolved.media.publicBaseUrl.split("/").filter(Boolean).join("/")}` : "";
|
|
@@ -1867,14 +2633,20 @@ const module$1 = defineNuxtModule({
|
|
|
1867
2633
|
localRoot: mediaLocalRoot,
|
|
1868
2634
|
dir: mediaDir,
|
|
1869
2635
|
...existingConfig.media ?? {}
|
|
1870
|
-
}
|
|
2636
|
+
},
|
|
2637
|
+
webhooks: {
|
|
2638
|
+
...webhooks.webhooks,
|
|
2639
|
+
...existingConfig.webhooks ?? {}
|
|
2640
|
+
},
|
|
2641
|
+
seo: { sitemap: false, robots: false, robotsSitemap: false }
|
|
1871
2642
|
};
|
|
1872
2643
|
nuxt.options.runtimeConfig.public.cms = {
|
|
1873
2644
|
mediaBaseUrl: resolved.media.publicBaseUrl,
|
|
1874
2645
|
mediaStorage: resolved.media.storage,
|
|
1875
2646
|
mediaMaxFileSize: resolved.media.maxFileSize,
|
|
1876
2647
|
i18n: resolved.i18n,
|
|
1877
|
-
previewPath
|
|
2648
|
+
previewPath,
|
|
2649
|
+
version: MODULE_VERSION
|
|
1878
2650
|
};
|
|
1879
2651
|
addTypeTemplate(
|
|
1880
2652
|
{
|
|
@@ -1887,7 +2659,7 @@ const module$1 = defineNuxtModule({
|
|
|
1887
2659
|
" id?: string",
|
|
1888
2660
|
" email: string",
|
|
1889
2661
|
" name?: string | null",
|
|
1890
|
-
" role?:
|
|
2662
|
+
" role?: string",
|
|
1891
2663
|
" firstLogin?: boolean",
|
|
1892
2664
|
" passwordStamp?: string",
|
|
1893
2665
|
" }",
|
|
@@ -1913,6 +2685,25 @@ const module$1 = defineNuxtModule({
|
|
|
1913
2685
|
},
|
|
1914
2686
|
{ nuxt: true, nitro: true }
|
|
1915
2687
|
);
|
|
2688
|
+
const hooksTypes = toPosix(resolver.resolve("./runtime/shared/hooks"));
|
|
2689
|
+
addTypeTemplate(
|
|
2690
|
+
{
|
|
2691
|
+
filename: "types/nuxt-cms-hooks.d.ts",
|
|
2692
|
+
getContents: () => [
|
|
2693
|
+
`import type { CmsRuntimeHooks } from '${hooksTypes}'`,
|
|
2694
|
+
"",
|
|
2695
|
+
...["nitropack", "nitropack/types"].flatMap((name) => [
|
|
2696
|
+
`declare module '${name}' {`,
|
|
2697
|
+
" interface NitroRuntimeHooks extends CmsRuntimeHooks {}",
|
|
2698
|
+
"}",
|
|
2699
|
+
""
|
|
2700
|
+
]),
|
|
2701
|
+
"export {}",
|
|
2702
|
+
""
|
|
2703
|
+
].join("\n")
|
|
2704
|
+
},
|
|
2705
|
+
{ nuxt: true, nitro: true }
|
|
2706
|
+
);
|
|
1916
2707
|
const brandPaths = {
|
|
1917
2708
|
rootDir: nuxt.options.rootDir,
|
|
1918
2709
|
srcDir: nuxt.options.srcDir ?? nuxt.options.rootDir
|
|
@@ -1944,12 +2735,12 @@ const module$1 = defineNuxtModule({
|
|
|
1944
2735
|
);
|
|
1945
2736
|
});
|
|
1946
2737
|
nuxt.hook("modules:done", async () => {
|
|
1947
|
-
const
|
|
1948
|
-
|
|
2738
|
+
const optimizeDeps = nuxt.options.vite.optimizeDeps ??= {};
|
|
2739
|
+
const include = [.../* @__PURE__ */ new Set([...optimizeDeps.include ?? [], "zod"])];
|
|
1949
2740
|
const rootUrl = pathToFileURL(`${nuxt.options.rootDir}/`).href;
|
|
1950
|
-
|
|
2741
|
+
optimizeDeps.include = await Promise.all(
|
|
1951
2742
|
include.map(
|
|
1952
|
-
async (entry) => entry.startsWith("@tiptap/") && !await tryResolveModule(entry, rootUrl) ? `@xleddyl/nuxt-cms > ${entry}` : entry
|
|
2743
|
+
async (entry) => (entry.startsWith("@tiptap/") || entry === "zod") && !await tryResolveModule(entry, rootUrl) ? `@xleddyl/nuxt-cms > ${entry}` : entry
|
|
1953
2744
|
)
|
|
1954
2745
|
);
|
|
1955
2746
|
});
|
|
@@ -2092,6 +2883,11 @@ const module$1 = defineNuxtModule({
|
|
|
2092
2883
|
handler: resolver.resolve("./runtime/server/routes/media-file")
|
|
2093
2884
|
});
|
|
2094
2885
|
}
|
|
2886
|
+
addServerHandler({
|
|
2887
|
+
route: "/api/cms/link-usage",
|
|
2888
|
+
method: "post",
|
|
2889
|
+
handler: resolver.resolve("./runtime/server/api/link-usage.post")
|
|
2890
|
+
});
|
|
2095
2891
|
addServerHandler({
|
|
2096
2892
|
route: "/api/cms/search",
|
|
2097
2893
|
method: "get",
|
|
@@ -2132,6 +2928,74 @@ const module$1 = defineNuxtModule({
|
|
|
2132
2928
|
method: "post",
|
|
2133
2929
|
handler: resolver.resolve("./runtime/server/api/account-welcome.post")
|
|
2134
2930
|
});
|
|
2931
|
+
addServerHandler({
|
|
2932
|
+
route: "/api/cms/site",
|
|
2933
|
+
method: "get",
|
|
2934
|
+
handler: resolver.resolve("./runtime/server/api/site.get")
|
|
2935
|
+
});
|
|
2936
|
+
addServerHandler({
|
|
2937
|
+
route: "/api/cms/site",
|
|
2938
|
+
method: "put",
|
|
2939
|
+
handler: resolver.resolve("./runtime/server/api/site.put")
|
|
2940
|
+
});
|
|
2941
|
+
addServerHandler({
|
|
2942
|
+
middleware: true,
|
|
2943
|
+
handler: resolver.resolve("./runtime/server/middleware/hidden-site")
|
|
2944
|
+
});
|
|
2945
|
+
addServerHandler({
|
|
2946
|
+
middleware: true,
|
|
2947
|
+
handler: resolver.resolve("./runtime/server/middleware/redirects")
|
|
2948
|
+
});
|
|
2949
|
+
addServerHandler({
|
|
2950
|
+
middleware: true,
|
|
2951
|
+
handler: resolver.resolve("./runtime/server/middleware/preview-links")
|
|
2952
|
+
});
|
|
2953
|
+
addServerHandler({
|
|
2954
|
+
route: "/api/cms/preview",
|
|
2955
|
+
method: "get",
|
|
2956
|
+
handler: resolver.resolve("./runtime/server/api/preview.get")
|
|
2957
|
+
});
|
|
2958
|
+
addServerHandler({
|
|
2959
|
+
route: "/api/cms/preview-links/:collection/:id",
|
|
2960
|
+
method: "get",
|
|
2961
|
+
handler: resolver.resolve("./runtime/server/api/preview-links.get")
|
|
2962
|
+
});
|
|
2963
|
+
addServerHandler({
|
|
2964
|
+
route: "/api/cms/preview-links/:collection/:id",
|
|
2965
|
+
method: "post",
|
|
2966
|
+
handler: resolver.resolve("./runtime/server/api/preview-links.post")
|
|
2967
|
+
});
|
|
2968
|
+
addServerHandler({
|
|
2969
|
+
route: "/api/cms/preview-links/:collection/:id/:linkId",
|
|
2970
|
+
method: "delete",
|
|
2971
|
+
handler: resolver.resolve("./runtime/server/api/preview-link.delete")
|
|
2972
|
+
});
|
|
2973
|
+
addServerHandler({
|
|
2974
|
+
route: "/api/cms/redirects",
|
|
2975
|
+
method: "get",
|
|
2976
|
+
handler: resolver.resolve("./runtime/server/api/redirects.get")
|
|
2977
|
+
});
|
|
2978
|
+
addServerHandler({
|
|
2979
|
+
route: "/api/cms/redirects",
|
|
2980
|
+
method: "post",
|
|
2981
|
+
handler: resolver.resolve("./runtime/server/api/redirects.post")
|
|
2982
|
+
});
|
|
2983
|
+
addServerHandler({
|
|
2984
|
+
route: "/api/cms/redirects/import",
|
|
2985
|
+
method: "post",
|
|
2986
|
+
handler: resolver.resolve("./runtime/server/api/redirects-import.post")
|
|
2987
|
+
});
|
|
2988
|
+
addServerHandler({
|
|
2989
|
+
route: "/api/cms/redirects/:id",
|
|
2990
|
+
method: "put",
|
|
2991
|
+
handler: resolver.resolve("./runtime/server/api/redirect.put")
|
|
2992
|
+
});
|
|
2993
|
+
addServerHandler({
|
|
2994
|
+
route: "/api/cms/redirects/:id",
|
|
2995
|
+
method: "delete",
|
|
2996
|
+
handler: resolver.resolve("./runtime/server/api/redirect.delete")
|
|
2997
|
+
});
|
|
2998
|
+
setupSeoRoutes(nuxt, resolver, options.seo, publicDir, logger);
|
|
2135
2999
|
addServerHandler({
|
|
2136
3000
|
route: "/api/cms/admin/settings",
|
|
2137
3001
|
method: "get",
|
|
@@ -2147,6 +3011,31 @@ const module$1 = defineNuxtModule({
|
|
|
2147
3011
|
method: "delete",
|
|
2148
3012
|
handler: resolver.resolve("./runtime/server/api/settings.delete")
|
|
2149
3013
|
});
|
|
3014
|
+
addServerHandler({
|
|
3015
|
+
route: "/api/cms/presets",
|
|
3016
|
+
method: "get",
|
|
3017
|
+
handler: resolver.resolve("./runtime/server/api/presets.get")
|
|
3018
|
+
});
|
|
3019
|
+
addServerHandler({
|
|
3020
|
+
route: "/api/cms/presets/:id",
|
|
3021
|
+
method: "put",
|
|
3022
|
+
handler: resolver.resolve("./runtime/server/api/preset.put")
|
|
3023
|
+
});
|
|
3024
|
+
addServerHandler({
|
|
3025
|
+
route: "/api/cms/presets/:id",
|
|
3026
|
+
method: "delete",
|
|
3027
|
+
handler: resolver.resolve("./runtime/server/api/preset.delete")
|
|
3028
|
+
});
|
|
3029
|
+
addServerHandler({
|
|
3030
|
+
route: "/api/cms/presets/:collection/:field",
|
|
3031
|
+
method: "get",
|
|
3032
|
+
handler: resolver.resolve("./runtime/server/api/presets-field.get")
|
|
3033
|
+
});
|
|
3034
|
+
addServerHandler({
|
|
3035
|
+
route: "/api/cms/presets/:collection/:field",
|
|
3036
|
+
method: "post",
|
|
3037
|
+
handler: resolver.resolve("./runtime/server/api/presets-field.post")
|
|
3038
|
+
});
|
|
2150
3039
|
addServerHandler({
|
|
2151
3040
|
route: "/api/cms/tags/:collection/:field",
|
|
2152
3041
|
method: "get",
|