bsmnt 0.9.0 → 0.11.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/dist/application/add-integration/index.d.ts.map +1 -1
- package/dist/application/add-integration/index.js +19 -2
- package/dist/application/add-integration/index.js.map +1 -1
- package/dist/application/shared/node-version.js.map +1 -1
- package/dist/core/create/internal-ids.d.ts +3 -0
- package/dist/core/create/internal-ids.d.ts.map +1 -1
- package/dist/core/create/internal-ids.js +4 -0
- package/dist/core/create/internal-ids.js.map +1 -1
- package/dist/domain/agents.d.ts.map +1 -1
- package/dist/domain/animations.d.ts.map +1 -1
- package/dist/domain/cms.d.ts.map +1 -1
- package/dist/domain/forms.d.ts.map +1 -1
- package/dist/domain/hooks.d.ts.map +1 -1
- package/dist/domain/skills.d.ts.map +1 -1
- package/dist/domain/templates.d.ts +10 -0
- package/dist/domain/templates.d.ts.map +1 -1
- package/dist/domain/templates.js +5 -0
- package/dist/domain/templates.js.map +1 -1
- package/dist/index.js +18 -5
- package/dist/index.js.map +1 -1
- package/dist/infrastructure/agents/register-sanity-mcp.js +3 -8
- package/dist/infrastructure/agents/register-sanity-mcp.js.map +1 -1
- package/dist/infrastructure/agents/register-storybook-mcp.d.ts +18 -0
- package/dist/infrastructure/agents/register-storybook-mcp.d.ts.map +1 -0
- package/dist/infrastructure/agents/register-storybook-mcp.js +157 -0
- package/dist/infrastructure/agents/register-storybook-mcp.js.map +1 -0
- package/dist/infrastructure/create/lifecycle-registry.d.ts.map +1 -1
- package/dist/infrastructure/create/lifecycle-registry.js +39 -1
- package/dist/infrastructure/create/lifecycle-registry.js.map +1 -1
- package/dist/infrastructure/create/merge-registry.d.ts.map +1 -1
- package/dist/infrastructure/create/merge-registry.js +4 -0
- package/dist/infrastructure/create/merge-registry.js.map +1 -1
- package/dist/infrastructure/create/sanity-auth.js.map +1 -1
- package/dist/infrastructure/create/setup-agent.d.ts +6 -1
- package/dist/infrastructure/create/setup-agent.d.ts.map +1 -1
- package/dist/infrastructure/create/setup-agent.js +23 -21
- package/dist/infrastructure/create/setup-agent.js.map +1 -1
- package/dist/infrastructure/create/setup-dotenvx.js.map +1 -1
- package/dist/infrastructure/create/setup-remote.js.map +1 -1
- package/dist/infrastructure/create/setup-sanity.d.ts.map +1 -1
- package/dist/infrastructure/create/setup-sanity.js +12 -1
- package/dist/infrastructure/create/setup-sanity.js.map +1 -1
- package/dist/infrastructure/integrations/ensure-studio-headers.d.ts +6 -0
- package/dist/infrastructure/integrations/ensure-studio-headers.d.ts.map +1 -0
- package/dist/infrastructure/integrations/ensure-studio-headers.js +153 -0
- package/dist/infrastructure/integrations/ensure-studio-headers.js.map +1 -0
- package/dist/infrastructure/integrations/inject-integration.d.ts.map +1 -1
- package/dist/infrastructure/integrations/inject-integration.js +2 -0
- package/dist/infrastructure/integrations/inject-integration.js.map +1 -1
- package/dist/infrastructure/update/notifier.js.map +1 -1
- package/dist/modules/features/cms/index.d.ts.map +1 -1
- package/dist/modules/features/cms/index.js +2 -0
- package/dist/modules/features/cms/index.js.map +1 -1
- package/dist/modules/features/cms/sanity/config.d.ts.map +1 -1
- package/dist/modules/features/cms/sanity/config.js +3 -1
- package/dist/modules/features/cms/sanity/config.js.map +1 -1
- package/dist/modules/features/cms/sanity/index.d.ts.map +1 -1
- package/dist/modules/features/cms/sanity/index.js +10 -1
- package/dist/modules/features/cms/sanity/index.js.map +1 -1
- package/dist/modules/features/cms/sanity/mergers/check-integration.d.ts.map +1 -1
- package/dist/modules/features/cms/sanity/mergers/check-integration.js +15 -6
- package/dist/modules/features/cms/sanity/mergers/check-integration.js.map +1 -1
- package/dist/modules/features/cms/sanity/mergers/knip.d.ts.map +1 -1
- package/dist/modules/features/cms/sanity/mergers/knip.js +1 -0
- package/dist/modules/features/cms/sanity/mergers/knip.js.map +1 -1
- package/dist/modules/features/cms/sanity/mergers/layout.js.map +1 -1
- package/dist/modules/features/cms/sanity/mergers/package-json.d.ts +12 -0
- package/dist/modules/features/cms/sanity/mergers/package-json.d.ts.map +1 -0
- package/dist/modules/features/cms/sanity/mergers/package-json.js +19 -0
- package/dist/modules/features/cms/sanity/mergers/package-json.js.map +1 -0
- package/dist/modules/features/cms/sanity-pagebuilder/config.d.ts.map +1 -1
- package/dist/modules/features/cms/sanity-pagebuilder/config.js +2 -1
- package/dist/modules/features/cms/sanity-pagebuilder/config.js.map +1 -1
- package/dist/modules/features/cms/sanity-pagebuilder/index.d.ts.map +1 -1
- package/dist/modules/features/cms/sanity-pagebuilder/index.js +7 -1
- package/dist/modules/features/cms/sanity-pagebuilder/index.js.map +1 -1
- package/dist/modules/features/cms/sanity-pagebuilder-cache/config.d.ts +14 -0
- package/dist/modules/features/cms/sanity-pagebuilder-cache/config.d.ts.map +1 -0
- package/dist/modules/features/cms/sanity-pagebuilder-cache/config.js +50 -0
- package/dist/modules/features/cms/sanity-pagebuilder-cache/config.js.map +1 -0
- package/dist/modules/features/cms/sanity-pagebuilder-cache/index.d.ts +3 -0
- package/dist/modules/features/cms/sanity-pagebuilder-cache/index.d.ts.map +1 -0
- package/dist/modules/features/cms/sanity-pagebuilder-cache/index.js +55 -0
- package/dist/modules/features/cms/sanity-pagebuilder-cache/index.js.map +1 -0
- package/dist/modules/presets/index.d.ts.map +1 -1
- package/dist/modules/presets/index.js +2 -0
- package/dist/modules/presets/index.js.map +1 -1
- package/dist/modules/presets/next-pagebuilder-cache.d.ts +3 -0
- package/dist/modules/presets/next-pagebuilder-cache.d.ts.map +1 -0
- package/dist/modules/presets/next-pagebuilder-cache.js +10 -0
- package/dist/modules/presets/next-pagebuilder-cache.js.map +1 -0
- package/dist/paths.d.ts +6 -0
- package/dist/paths.d.ts.map +1 -1
- package/dist/paths.js +6 -0
- package/dist/paths.js.map +1 -1
- package/package.json +6 -3
- package/src/modules/features/cms/sanity/files/app/api/blog/[slug]/route.ts +4 -1
- package/src/modules/features/cms/sanity/files/app/sitemap.md/route.ts +2 -1
- package/src/modules/features/cms/sanity/files/components/ui/sanity-image/index.tsx +19 -5
- package/src/modules/features/cms/sanity/files/lib/integrations/check-integration.ts +7 -6
- package/src/modules/features/cms/sanity/files/lib/integrations/sanity/README.md +32 -1
- package/src/modules/features/cms/sanity/files/lib/integrations/sanity/components/rich-text.tsx +1 -0
- package/src/modules/features/cms/sanity/files/lib/integrations/sanity/env.ts +2 -2
- package/src/modules/features/cms/sanity/files/proxy.ts +9 -0
- package/src/modules/features/cms/sanity-pagebuilder/files/app/api/[[...slug]]/route.ts +12 -3
- package/src/modules/features/cms/sanity-pagebuilder/files/app/api/revalidate/route.ts +172 -24
- package/src/modules/features/cms/sanity-pagebuilder/files/app/feed.xml/route.ts +14 -7
- package/src/modules/features/cms/sanity-pagebuilder/files/app/sitemap.md/route.ts +8 -2
- package/src/modules/features/cms/sanity-pagebuilder/files/app/studio/[[...tool]]/page.tsx +16 -1
- package/src/modules/features/cms/sanity-pagebuilder/files/components/sanity/rich-text.tsx +1 -0
- package/src/modules/features/cms/sanity-pagebuilder/files/components/sanity/visual-editing.tsx +2 -2
- package/src/modules/features/cms/sanity-pagebuilder/files/components/ui/sanity-image/index.tsx +15 -5
- package/src/modules/features/cms/sanity-pagebuilder/files/lib/integrations/check-integration.ts +6 -5
- package/src/modules/features/cms/sanity-pagebuilder/files/lib/integrations/sanity/client.ts +8 -0
- package/src/modules/features/cms/sanity-pagebuilder/files/lib/integrations/sanity/components/slug-field.tsx +13 -7
- package/src/modules/features/cms/sanity-pagebuilder/files/lib/integrations/sanity/components/slug-notices.tsx +125 -0
- package/src/modules/features/cms/sanity-pagebuilder/files/lib/integrations/sanity/env.ts +2 -2
- package/src/modules/features/cms/sanity-pagebuilder/files/lib/integrations/sanity/fetchers/layout.ts +22 -52
- package/src/modules/features/cms/sanity-pagebuilder/files/lib/integrations/sanity/icons.ts +2 -2
- package/src/modules/features/cms/sanity-pagebuilder/files/lib/integrations/sanity/live/index.tsx +4 -2
- package/src/modules/features/cms/sanity-pagebuilder/files/lib/integrations/sanity/markdown-proxy.config.ts +1 -1
- package/src/modules/features/cms/sanity-pagebuilder/files/lib/integrations/sanity/page-builder-config.ts +7 -0
- package/src/modules/features/cms/sanity-pagebuilder/files/lib/integrations/sanity/page-tree-pane.tsx +70 -57
- package/src/modules/features/cms/sanity-pagebuilder/files/lib/integrations/sanity/queries.ts +119 -29
- package/src/modules/features/cms/sanity-pagebuilder/files/lib/integrations/sanity/revalidate-paths.ts +70 -0
- package/src/modules/features/cms/sanity-pagebuilder/files/lib/integrations/sanity/schemas/components/index.ts +1 -0
- package/src/modules/features/cms/sanity-pagebuilder/files/lib/integrations/sanity/schemas/components/reusable/rich-text-block.ts +39 -0
- package/src/modules/features/cms/sanity-pagebuilder/files/lib/integrations/sanity/schemas/index.ts +2 -0
- package/src/modules/features/cms/sanity-pagebuilder/files/lib/integrations/sanity/schemas/layout/redirect.ts +35 -4
- package/src/modules/features/cms/sanity-pagebuilder/files/lib/integrations/sanity/schemas/shared/page.ts +12 -0
- package/src/modules/features/cms/sanity-pagebuilder/files/lib/integrations/sanity/tags.ts +56 -0
- package/src/modules/features/cms/sanity-pagebuilder/files/lib/integrations/sanity/utils/page-builder-markdown.ts +12 -0
- package/src/modules/features/cms/sanity-pagebuilder/files/lib/utils/sitemap.ts +11 -8
- package/src/modules/features/cms/sanity-pagebuilder/files/proxy.ts +9 -0
- package/src/modules/features/cms/sanity-pagebuilder-cache/files/.knip.json +8 -0
- package/src/modules/features/cms/sanity-pagebuilder-cache/files/app/api/[[...slug]]/route.ts +112 -0
- package/src/modules/features/cms/sanity-pagebuilder-cache/files/app/api/draft-mode/disable/route.ts +7 -0
- package/src/modules/features/cms/sanity-pagebuilder-cache/files/app/api/draft-mode/enable/route.ts +20 -0
- package/src/modules/features/cms/sanity-pagebuilder-cache/files/app/api/revalidate/route.ts +292 -0
- package/src/modules/features/cms/sanity-pagebuilder-cache/files/app/feed.xml/route.ts +66 -0
- package/src/modules/features/cms/sanity-pagebuilder-cache/files/app/sitemap.md/route.ts +132 -0
- package/src/modules/features/cms/sanity-pagebuilder-cache/files/app/studio/[[...tool]]/layout.tsx +26 -0
- package/src/modules/features/cms/sanity-pagebuilder-cache/files/app/studio/[[...tool]]/page.tsx +23 -0
- package/src/modules/features/cms/sanity-pagebuilder-cache/files/components/sanity/draft-mode-toggle.tsx +27 -0
- package/src/modules/features/cms/sanity-pagebuilder-cache/files/components/sanity/rich-text.tsx +88 -0
- package/src/modules/features/cms/sanity-pagebuilder-cache/files/components/sanity/visual-editing.tsx +11 -0
- package/src/modules/features/cms/sanity-pagebuilder-cache/files/components/ui/sanity-image/index.tsx +51 -0
- package/src/modules/features/cms/sanity-pagebuilder-cache/files/lib/integrations/check-integration.ts +6 -0
- package/src/modules/features/cms/sanity-pagebuilder-cache/files/lib/integrations/sanity/client.ts +37 -0
- package/src/modules/features/cms/sanity-pagebuilder-cache/files/lib/integrations/sanity/components/block-actions-item.tsx +81 -0
- package/src/modules/features/cms/sanity-pagebuilder-cache/files/lib/integrations/sanity/components/disable-draft-mode.tsx +23 -0
- package/src/modules/features/cms/sanity-pagebuilder-cache/files/lib/integrations/sanity/components/page-builder-input.tsx +39 -0
- package/src/modules/features/cms/sanity-pagebuilder-cache/files/lib/integrations/sanity/components/page-category-input.tsx +50 -0
- package/src/modules/features/cms/sanity-pagebuilder-cache/files/lib/integrations/sanity/components/same-as-input.tsx +66 -0
- package/src/modules/features/cms/sanity-pagebuilder-cache/files/lib/integrations/sanity/components/slug-field.tsx +24 -0
- package/src/modules/features/cms/sanity-pagebuilder-cache/files/lib/integrations/sanity/components/slug-input.tsx +14 -0
- package/src/modules/features/cms/sanity-pagebuilder-cache/files/lib/integrations/sanity/components/slug-notices.tsx +125 -0
- package/src/modules/features/cms/sanity-pagebuilder-cache/files/lib/integrations/sanity/confirm-publish-action.ts +40 -0
- package/src/modules/features/cms/sanity-pagebuilder-cache/files/lib/integrations/sanity/env.ts +34 -0
- package/src/modules/features/cms/sanity-pagebuilder-cache/files/lib/integrations/sanity/fetchers/layout.ts +39 -0
- package/src/modules/features/cms/sanity-pagebuilder-cache/files/lib/integrations/sanity/fetchers/redirects.ts +22 -0
- package/src/modules/features/cms/sanity-pagebuilder-cache/files/lib/integrations/sanity/icons.ts +81 -0
- package/src/modules/features/cms/sanity-pagebuilder-cache/files/lib/integrations/sanity/live/index.tsx +140 -0
- package/src/modules/features/cms/sanity-pagebuilder-cache/files/lib/integrations/sanity/markdown-proxy.config.ts +50 -0
- package/src/modules/features/cms/sanity-pagebuilder-cache/files/lib/integrations/sanity/page-builder-config.ts +144 -0
- package/src/modules/features/cms/sanity-pagebuilder-cache/files/lib/integrations/sanity/page-category.ts +28 -0
- package/src/modules/features/cms/sanity-pagebuilder-cache/files/lib/integrations/sanity/page-tree-pane.tsx +431 -0
- package/src/modules/features/cms/sanity-pagebuilder-cache/files/lib/integrations/sanity/pinned-pages.ts +54 -0
- package/src/modules/features/cms/sanity-pagebuilder-cache/files/lib/integrations/sanity/presentation.ts +119 -0
- package/src/modules/features/cms/sanity-pagebuilder-cache/files/lib/integrations/sanity/queries.ts +548 -0
- package/src/modules/features/cms/sanity-pagebuilder-cache/files/lib/integrations/sanity/revalidate-paths.ts +70 -0
- package/src/modules/features/cms/sanity-pagebuilder-cache/files/lib/integrations/sanity/sanity.cli.ts +29 -0
- package/src/modules/features/cms/sanity-pagebuilder-cache/files/lib/integrations/sanity/sanity.config.ts +116 -0
- package/src/modules/features/cms/sanity-pagebuilder-cache/files/lib/integrations/sanity/schemas/components/index.ts +6 -0
- package/src/modules/features/cms/sanity-pagebuilder-cache/files/lib/integrations/sanity/schemas/components/reusable/blog-content.ts +89 -0
- package/src/modules/features/cms/sanity-pagebuilder-cache/files/lib/integrations/sanity/schemas/components/reusable/description.ts +29 -0
- package/src/modules/features/cms/sanity-pagebuilder-cache/files/lib/integrations/sanity/schemas/components/reusable/gated-content.ts +178 -0
- package/src/modules/features/cms/sanity-pagebuilder-cache/files/lib/integrations/sanity/schemas/components/reusable/hero.ts +28 -0
- package/src/modules/features/cms/sanity-pagebuilder-cache/files/lib/integrations/sanity/schemas/components/reusable/rich-text-block.ts +39 -0
- package/src/modules/features/cms/sanity-pagebuilder-cache/files/lib/integrations/sanity/schemas/components/singleton/content-collection.ts +45 -0
- package/src/modules/features/cms/sanity-pagebuilder-cache/files/lib/integrations/sanity/schemas/content/author.ts +70 -0
- package/src/modules/features/cms/sanity-pagebuilder-cache/files/lib/integrations/sanity/schemas/content/blog-category.ts +55 -0
- package/src/modules/features/cms/sanity-pagebuilder-cache/files/lib/integrations/sanity/schemas/index.ts +134 -0
- package/src/modules/features/cms/sanity-pagebuilder-cache/files/lib/integrations/sanity/schemas/layout/company-data.ts +70 -0
- package/src/modules/features/cms/sanity-pagebuilder-cache/files/lib/integrations/sanity/schemas/layout/footer.ts +79 -0
- package/src/modules/features/cms/sanity-pagebuilder-cache/files/lib/integrations/sanity/schemas/layout/navbar.ts +75 -0
- package/src/modules/features/cms/sanity-pagebuilder-cache/files/lib/integrations/sanity/schemas/layout/redirect.ts +152 -0
- package/src/modules/features/cms/sanity-pagebuilder-cache/files/lib/integrations/sanity/schemas/layout/structured-data.ts +124 -0
- package/src/modules/features/cms/sanity-pagebuilder-cache/files/lib/integrations/sanity/schemas/shared/link.ts +125 -0
- package/src/modules/features/cms/sanity-pagebuilder-cache/files/lib/integrations/sanity/schemas/shared/logo-field.ts +9 -0
- package/src/modules/features/cms/sanity-pagebuilder-cache/files/lib/integrations/sanity/schemas/shared/metadata.ts +132 -0
- package/src/modules/features/cms/sanity-pagebuilder-cache/files/lib/integrations/sanity/schemas/shared/nav-objects.ts +192 -0
- package/src/modules/features/cms/sanity-pagebuilder-cache/files/lib/integrations/sanity/schemas/shared/page-builder.ts +43 -0
- package/src/modules/features/cms/sanity-pagebuilder-cache/files/lib/integrations/sanity/schemas/shared/page-folder.ts +124 -0
- package/src/modules/features/cms/sanity-pagebuilder-cache/files/lib/integrations/sanity/schemas/shared/page.ts +265 -0
- package/src/modules/features/cms/sanity-pagebuilder-cache/files/lib/integrations/sanity/schemas/shared/richText.ts +63 -0
- package/src/modules/features/cms/sanity-pagebuilder-cache/files/lib/integrations/sanity/schemas/shared/structured-data-blocks.ts +436 -0
- package/src/modules/features/cms/sanity-pagebuilder-cache/files/lib/integrations/sanity/singletons.ts +51 -0
- package/src/modules/features/cms/sanity-pagebuilder-cache/files/lib/integrations/sanity/structure.ts +334 -0
- package/src/modules/features/cms/sanity-pagebuilder-cache/files/lib/integrations/sanity/tags.ts +89 -0
- package/src/modules/features/cms/sanity-pagebuilder-cache/files/lib/integrations/sanity/utils/image.ts +8 -0
- package/src/modules/features/cms/sanity-pagebuilder-cache/files/lib/integrations/sanity/utils/link.ts +140 -0
- package/src/modules/features/cms/sanity-pagebuilder-cache/files/lib/integrations/sanity/utils/page-builder-markdown.ts +113 -0
- package/src/modules/features/cms/sanity-pagebuilder-cache/files/lib/integrations/sanity/utils/page-json-ld.ts +208 -0
- package/src/modules/features/cms/sanity-pagebuilder-cache/files/lib/scripts/sanity-typegen.ts +36 -0
- package/src/modules/features/cms/sanity-pagebuilder-cache/files/lib/utils/check-redirect.ts +30 -0
- package/src/modules/features/cms/sanity-pagebuilder-cache/files/lib/utils/json-ld.tsx +522 -0
- package/src/modules/features/cms/sanity-pagebuilder-cache/files/lib/utils/metadata.ts +189 -0
- package/src/modules/features/cms/sanity-pagebuilder-cache/files/lib/utils/rss.ts +96 -0
- package/src/modules/features/cms/sanity-pagebuilder-cache/files/lib/utils/sitemap.ts +58 -0
- package/src/modules/features/cms/sanity-pagebuilder-cache/files/proxy.ts +90 -0
- package/src/modules/features/env/dotenvx/files/vercelignore +5 -0
- package/src/templates/next-default/.env.example +4 -2
- package/src/templates/next-default/.github/workflows/ci.yml +1 -1
- package/src/templates/next-default/.vscode/settings.json +0 -2
- package/src/templates/next-default/app/robots.ts +2 -3
- package/src/templates/next-default/components/ui/image/index.tsx +2 -4
- package/src/templates/next-default/knip.json +1 -1
- package/src/templates/next-default/lefthook.yml +1 -1
- package/src/templates/next-default/lib/utils/README.md +18 -5
- package/src/templates/next-default/lib/utils/image-sizes.ts +72 -0
- package/src/templates/next-default/lib/utils/merge-refs.ts +56 -0
- package/src/templates/next-default/lib/utils/strings.ts +1 -51
- package/src/templates/next-default/next.config.ts +38 -5
- package/src/templates/next-default/package.json +9 -12
- package/src/templates/next-experiments/.env.example +4 -2
- package/src/templates/next-experiments/.github/workflows/ci.yml +1 -1
- package/src/templates/next-experiments/.vscode/settings.json +0 -2
- package/src/templates/next-experiments/app/page.tsx +1 -1
- package/src/templates/next-experiments/app/robots.ts +2 -3
- package/src/templates/next-experiments/components/ui/image/index.tsx +2 -4
- package/src/templates/next-experiments/knip.json +1 -1
- package/src/templates/next-experiments/lefthook.yml +1 -1
- package/src/templates/next-experiments/lib/utils/README.md +18 -5
- package/src/templates/next-experiments/lib/utils/image-sizes.ts +72 -0
- package/src/templates/next-experiments/lib/utils/merge-refs.ts +56 -0
- package/src/templates/next-experiments/lib/utils/strings.ts +1 -51
- package/src/templates/next-experiments/next.config.ts +38 -5
- package/src/templates/next-experiments/package.json +9 -12
- package/src/templates/next-pagebuilder/.env.example +3 -0
- package/src/templates/next-pagebuilder/.github/workflows/ci.yml +1 -1
- package/src/templates/next-pagebuilder/.vscode/settings.json +0 -2
- package/src/templates/next-pagebuilder/README.md +95 -0
- package/src/templates/next-pagebuilder/app/(content)/[[...slug]]/page.tsx +24 -9
- package/src/templates/next-pagebuilder/app/robots.ts +2 -3
- package/src/templates/next-pagebuilder/app/sitemap.xml/route.ts +9 -4
- package/src/templates/next-pagebuilder/biome.json +2 -1
- package/src/templates/next-pagebuilder/components/layout/footer/index.tsx +3 -6
- package/src/templates/next-pagebuilder/components/layout/header/index.tsx +3 -2
- package/src/templates/next-pagebuilder/components/layout/json-ld/index.tsx +3 -8
- package/src/templates/next-pagebuilder/components/page-builder/components/content-collection/content-filters.tsx +23 -40
- package/src/templates/next-pagebuilder/components/page-builder/components/content-collection/index.tsx +88 -58
- package/src/templates/next-pagebuilder/components/page-builder/components/content-collection/skeleton.tsx +41 -0
- package/src/templates/next-pagebuilder/components/page-builder/components/content-collection/types.ts +14 -1
- package/src/templates/next-pagebuilder/components/page-builder/components/rich-text-block.tsx +16 -0
- package/src/templates/next-pagebuilder/components/page-builder/renderer.tsx +23 -3
- package/src/templates/next-pagebuilder/components/page-document/index.tsx +1 -0
- package/src/templates/next-pagebuilder/components/ui/image/index.tsx +2 -1
- package/src/templates/next-pagebuilder/knip.json +3 -2
- package/src/templates/next-pagebuilder/lefthook.yml +1 -1
- package/src/templates/next-pagebuilder/lib/integrations/sanity/sanity.types.ts +1221 -977
- package/src/templates/next-pagebuilder/lib/utils/image-sizes.ts +72 -0
- package/src/templates/next-pagebuilder/lib/utils/merge-refs.ts +56 -0
- package/src/templates/next-pagebuilder/next.config.ts +35 -5
- package/src/templates/next-pagebuilder/package.json +8 -10
- package/src/templates/next-pagebuilder-cache/.biome/plugins/README.md +21 -0
- package/src/templates/next-pagebuilder-cache/.biome/plugins/no-anchor-element.grit +12 -0
- package/src/templates/next-pagebuilder-cache/.biome/plugins/no-relative-parent-imports.grit +10 -0
- package/src/templates/next-pagebuilder-cache/.biome/plugins/no-unnecessary-forwardref.grit +9 -0
- package/src/templates/next-pagebuilder-cache/.env.example +40 -0
- package/src/templates/next-pagebuilder-cache/.github/PULL_REQUEST_TEMPLATE.md +13 -0
- package/src/templates/next-pagebuilder-cache/.github/workflows/ci.yml +105 -0
- package/src/templates/next-pagebuilder-cache/.vscode/extensions.json +19 -0
- package/src/templates/next-pagebuilder-cache/.vscode/settings.json +103 -0
- package/src/templates/next-pagebuilder-cache/.zed/settings.json +47 -0
- package/src/templates/next-pagebuilder-cache/README.md +212 -0
- package/src/templates/next-pagebuilder-cache/_gitignore +65 -0
- package/src/templates/next-pagebuilder-cache/app/(content)/[[...slug]]/page.tsx +124 -0
- package/src/templates/next-pagebuilder-cache/app/(content)/layout.tsx +28 -0
- package/src/templates/next-pagebuilder-cache/app/actions/unlock-gated-content.ts +53 -0
- package/src/templates/next-pagebuilder-cache/app/favicon.ico +0 -0
- package/src/templates/next-pagebuilder-cache/app/layout.tsx +83 -0
- package/src/templates/next-pagebuilder-cache/app/robots.ts +13 -0
- package/src/templates/next-pagebuilder-cache/app/sitemap.xml/route.ts +84 -0
- package/src/templates/next-pagebuilder-cache/biome.json +257 -0
- package/src/templates/next-pagebuilder-cache/components/layout/footer/index.tsx +127 -0
- package/src/templates/next-pagebuilder-cache/components/layout/header/components/cta-button.tsx +28 -0
- package/src/templates/next-pagebuilder-cache/components/layout/header/components/mega-menu-panel.tsx +90 -0
- package/src/templates/next-pagebuilder-cache/components/layout/header/components/nav-item-renderer.tsx +98 -0
- package/src/templates/next-pagebuilder-cache/components/layout/header/components/nav-leaf-item.tsx +33 -0
- package/src/templates/next-pagebuilder-cache/components/layout/header/components/types.ts +7 -0
- package/src/templates/next-pagebuilder-cache/components/layout/header/header-client.tsx +110 -0
- package/src/templates/next-pagebuilder-cache/components/layout/header/index.tsx +32 -0
- package/src/templates/next-pagebuilder-cache/components/layout/json-ld/index.tsx +79 -0
- package/src/templates/next-pagebuilder-cache/components/layout/wrapper/index.tsx +33 -0
- package/src/templates/next-pagebuilder-cache/components/page-builder/components/article-content/index.tsx +78 -0
- package/src/templates/next-pagebuilder-cache/components/page-builder/components/article-content/related-post-item.tsx +27 -0
- package/src/templates/next-pagebuilder-cache/components/page-builder/components/content-collection/content-card.tsx +66 -0
- package/src/templates/next-pagebuilder-cache/components/page-builder/components/content-collection/content-filters.tsx +76 -0
- package/src/templates/next-pagebuilder-cache/components/page-builder/components/content-collection/content-grid.tsx +40 -0
- package/src/templates/next-pagebuilder-cache/components/page-builder/components/content-collection/content-pagination-nav.tsx +71 -0
- package/src/templates/next-pagebuilder-cache/components/page-builder/components/content-collection/index.tsx +306 -0
- package/src/templates/next-pagebuilder-cache/components/page-builder/components/content-collection/skeleton.tsx +41 -0
- package/src/templates/next-pagebuilder-cache/components/page-builder/components/content-collection/types.ts +32 -0
- package/src/templates/next-pagebuilder-cache/components/page-builder/components/description.tsx +17 -0
- package/src/templates/next-pagebuilder-cache/components/page-builder/components/gated-content/index.tsx +169 -0
- package/src/templates/next-pagebuilder-cache/components/page-builder/components/hero.tsx +17 -0
- package/src/templates/next-pagebuilder-cache/components/page-builder/components/rich-text-block.tsx +16 -0
- package/src/templates/next-pagebuilder-cache/components/page-builder/renderer.tsx +71 -0
- package/src/templates/next-pagebuilder-cache/components/page-builder/types.ts +25 -0
- package/src/templates/next-pagebuilder-cache/components/page-document/index.tsx +147 -0
- package/src/templates/next-pagebuilder-cache/components/ui/image/index.tsx +216 -0
- package/src/templates/next-pagebuilder-cache/components/ui/link/index.tsx +152 -0
- package/src/templates/next-pagebuilder-cache/components.json +24 -0
- package/src/templates/next-pagebuilder-cache/knip.json +35 -0
- package/src/templates/next-pagebuilder-cache/lefthook.yml +25 -0
- package/src/templates/next-pagebuilder-cache/lib/integrations/sanity/sanity.types.ts +1588 -0
- package/src/templates/next-pagebuilder-cache/lib/scripts/setup-ci-secrets.ts +117 -0
- package/src/templates/next-pagebuilder-cache/lib/styles/cn.ts +5 -0
- package/src/templates/next-pagebuilder-cache/lib/styles/global.css +69 -0
- package/src/templates/next-pagebuilder-cache/lib/utils/base-url.ts +17 -0
- package/src/templates/next-pagebuilder-cache/lib/utils/format-date.ts +8 -0
- package/src/templates/next-pagebuilder-cache/lib/utils/slug-tag.ts +6 -0
- package/src/templates/next-pagebuilder-cache/next.config.ts +176 -0
- package/src/templates/next-pagebuilder-cache/package.json +62 -0
- package/src/templates/next-pagebuilder-cache/postcss.config.mjs +65 -0
- package/src/templates/next-pagebuilder-cache/svg.d.ts +6 -0
- package/src/templates/next-pagebuilder-cache/tsconfig.json +38 -0
- package/src/templates/next-webgl/.env.example +4 -2
- package/src/templates/next-webgl/.github/workflows/ci.yml +1 -1
- package/src/templates/next-webgl/.vscode/settings.json +0 -2
- package/src/templates/next-webgl/app/robots.ts +2 -3
- package/src/templates/next-webgl/components/ui/image/index.tsx +2 -4
- package/src/templates/next-webgl/knip.json +1 -1
- package/src/templates/next-webgl/lefthook.yml +1 -1
- package/src/templates/next-webgl/lib/utils/README.md +18 -5
- package/src/templates/next-webgl/lib/utils/image-sizes.ts +72 -0
- package/src/templates/next-webgl/lib/utils/merge-refs.ts +56 -0
- package/src/templates/next-webgl/lib/utils/strings.ts +1 -51
- package/src/templates/next-webgl/next.config.ts +38 -5
- package/src/templates/next-webgl/package.json +11 -14
- package/src/templates/next-experiments/tsconfig.tsbuildinfo +0 -1
- package/src/templates/next-pagebuilder/app/actions/refresh.ts +0 -5
- package/src/templates/next-webgl/tsconfig.tsbuildinfo +0 -1
|
@@ -65,7 +65,7 @@ export const redirect = defineType({
|
|
|
65
65
|
description:
|
|
66
66
|
"Where to send visitors. An internal route path without a leading slash (e.g. new-page or blog/new-post), or an absolute URL (e.g. https://example.com).",
|
|
67
67
|
validation: (Rule) =>
|
|
68
|
-
Rule.required().custom((value, context) => {
|
|
68
|
+
Rule.required().custom(async (value, context) => {
|
|
69
69
|
if (typeof value !== "string") return true;
|
|
70
70
|
|
|
71
71
|
const isAbsolute = /^https?:\/\//.test(value);
|
|
@@ -79,13 +79,44 @@ export const redirect = defineType({
|
|
|
79
79
|
return "Internal destination must not start or end with a forward slash";
|
|
80
80
|
}
|
|
81
81
|
|
|
82
|
-
const
|
|
83
|
-
|
|
84
|
-
|
|
82
|
+
const document = context.document as
|
|
83
|
+
| RedirectValidationDocument
|
|
84
|
+
| undefined;
|
|
85
|
+
const source = document?.source?.current;
|
|
86
|
+
|
|
85
87
|
if (source && source === value) {
|
|
86
88
|
return "Destination must differ from source";
|
|
87
89
|
}
|
|
88
90
|
|
|
91
|
+
// Rejects a direct loop (a -> b, b -> a); longer chains aren't checked.
|
|
92
|
+
// Normalizes slashes since docs written outside Studio can skip that validation.
|
|
93
|
+
if (source) {
|
|
94
|
+
const id = document?._id?.replace(/^drafts\./, "") ?? "";
|
|
95
|
+
const client = context.getClient({ apiVersion });
|
|
96
|
+
const normalize = (path: string) => path.replace(/^\/+|\/+$/g, "");
|
|
97
|
+
const normalizedSource = normalize(source);
|
|
98
|
+
const normalizedDestination = normalize(value);
|
|
99
|
+
const others = await client.fetch<
|
|
100
|
+
Array<{ source?: string; destination?: string }>
|
|
101
|
+
>(
|
|
102
|
+
`*[
|
|
103
|
+
_type == "redirect" &&
|
|
104
|
+
!(_id in [$id, "drafts." + $id])
|
|
105
|
+
]{ "source": source.current, destination }`,
|
|
106
|
+
{ id },
|
|
107
|
+
);
|
|
108
|
+
const hasInverse = others.some(
|
|
109
|
+
(redirect) =>
|
|
110
|
+
redirect.source &&
|
|
111
|
+
normalize(redirect.source) === normalizedDestination &&
|
|
112
|
+
redirect.destination &&
|
|
113
|
+
normalize(redirect.destination) === normalizedSource,
|
|
114
|
+
);
|
|
115
|
+
if (hasInverse) {
|
|
116
|
+
return "This creates a redirect loop — another redirect sends the destination back to this source";
|
|
117
|
+
}
|
|
118
|
+
}
|
|
119
|
+
|
|
89
120
|
return true;
|
|
90
121
|
}),
|
|
91
122
|
}),
|
|
@@ -204,6 +204,18 @@ export const page = defineType({
|
|
|
204
204
|
return `These page builder components do not belong to the "${pageType ?? "generic"}" category: ${invalidMemberTitles.join(", ")}`;
|
|
205
205
|
}),
|
|
206
206
|
}),
|
|
207
|
+
defineField({
|
|
208
|
+
name: "collectionCategory",
|
|
209
|
+
title: "Pinned Collection Category",
|
|
210
|
+
type: "reference",
|
|
211
|
+
to: [{ type: "blogCategory" }],
|
|
212
|
+
// Lives on the page because the collection block is deliberately a
|
|
213
|
+
// singleton (see `singletonComponents`) — editors reuse one block rather
|
|
214
|
+
// than creating them, so a category set there would apply site-wide. Move
|
|
215
|
+
// the block to the reusable list if you'd rather scope it per instance.
|
|
216
|
+
description:
|
|
217
|
+
"Turns this page into a category page: the content collection shows only this category. Leave empty to show everything.",
|
|
218
|
+
}),
|
|
207
219
|
defineField({
|
|
208
220
|
name: "hiddenPageBuilderKeys",
|
|
209
221
|
title: "Hidden Blocks",
|
|
@@ -0,0 +1,56 @@
|
|
|
1
|
+
// Dependency maps for the publish webhook. Dependency-free on purpose: the
|
|
2
|
+
// modules owning these documents pull in the Studio UI.
|
|
3
|
+
//
|
|
4
|
+
// The strings mirror the `tags: [...]` arrays at each fetch site. Nothing
|
|
5
|
+
// enforces that pairing, so a tag renamed there has to be renamed here.
|
|
6
|
+
|
|
7
|
+
const SANITY_NAVBAR_TAG = "navbar";
|
|
8
|
+
const SANITY_STRUCTURED_DATA_TAG = "structuredData";
|
|
9
|
+
|
|
10
|
+
// Crawler routes: broader than any per-slug tag.
|
|
11
|
+
export const SITEMAP_TAG = "sitemap";
|
|
12
|
+
export const FEED_TAG = "feed";
|
|
13
|
+
|
|
14
|
+
export const BLOG_COLLECTION_TAGS = [
|
|
15
|
+
"blog-categories",
|
|
16
|
+
"blog-articles",
|
|
17
|
+
"blog-articles-count",
|
|
18
|
+
] as const;
|
|
19
|
+
|
|
20
|
+
// Publishing a `page` revalidates its own path; nothing else reaches the cached
|
|
21
|
+
// collection windows.
|
|
22
|
+
export const BLOG_COLLECTION_TYPES = new Set<string>([
|
|
23
|
+
"page",
|
|
24
|
+
"blogContent",
|
|
25
|
+
"blogCategory",
|
|
26
|
+
"author",
|
|
27
|
+
]);
|
|
28
|
+
|
|
29
|
+
// Publishing the left-hand type has to bust the right-hand tags; nothing else
|
|
30
|
+
// reaches them.
|
|
31
|
+
export const CROSS_DOC_TAG_DEPENDENCIES: Record<string, readonly string[]> = {
|
|
32
|
+
// Redundant while LAYOUT_QUERY is merged; kept so splitting it can't lose this.
|
|
33
|
+
companyData: [SANITY_STRUCTURED_DATA_TAG],
|
|
34
|
+
// Nav links deref pages; sitemap and feed move whenever any page does.
|
|
35
|
+
page: [SANITY_NAVBAR_TAG, SITEMAP_TAG, FEED_TAG],
|
|
36
|
+
// ...plus the collections, which decide membership on the folder slug, so a
|
|
37
|
+
// rename changes which pages are articles at all.
|
|
38
|
+
pageFolder: [
|
|
39
|
+
SANITY_NAVBAR_TAG,
|
|
40
|
+
...BLOG_COLLECTION_TAGS,
|
|
41
|
+
SITEMAP_TAG,
|
|
42
|
+
FEED_TAG,
|
|
43
|
+
],
|
|
44
|
+
// The feed derefs the article's block, then its author and categories.
|
|
45
|
+
blogContent: [FEED_TAG],
|
|
46
|
+
author: [FEED_TAG],
|
|
47
|
+
blogCategory: [FEED_TAG],
|
|
48
|
+
};
|
|
49
|
+
|
|
50
|
+
// Types a page inlines by reference, so publishing one goes stale on every page
|
|
51
|
+
// pointing at it. `page` is here for `relatedPosts[]->` and rich-text links.
|
|
52
|
+
export const REFERENCEABLE_TYPES = new Set<string>([
|
|
53
|
+
"page",
|
|
54
|
+
"author",
|
|
55
|
+
"blogCategory",
|
|
56
|
+
]);
|
|
@@ -29,6 +29,18 @@ export const renderPageBuilderMarkdown = (
|
|
|
29
29
|
return block.headline ? [`## ${block.headline}`, ""] : [];
|
|
30
30
|
case "description":
|
|
31
31
|
return block.description ? [block.description, ""] : [];
|
|
32
|
+
case "richTextBlock":
|
|
33
|
+
return block.content
|
|
34
|
+
? [
|
|
35
|
+
portableTextToMarkdown(
|
|
36
|
+
block.content as Parameters<
|
|
37
|
+
typeof portableTextToMarkdown
|
|
38
|
+
>[0],
|
|
39
|
+
markdownOptions,
|
|
40
|
+
),
|
|
41
|
+
"",
|
|
42
|
+
]
|
|
43
|
+
: [];
|
|
32
44
|
case "gatedContent": {
|
|
33
45
|
// The gated payload is withheld from the page query, so it is not
|
|
34
46
|
// available here either — only the teaser is emitted, matching what
|
|
@@ -1,6 +1,7 @@
|
|
|
1
1
|
import { PINNED_PAGE_ENTRIES } from "@/lib/integrations/sanity/pinned-pages";
|
|
2
2
|
import { PAGE_SITEMAP_QUERY } from "@/lib/integrations/sanity/queries";
|
|
3
3
|
import type { PAGE_SITEMAP_QUERY_RESULT } from "@/lib/integrations/sanity/sanity.types";
|
|
4
|
+
import { SITEMAP_TAG } from "@/lib/integrations/sanity/tags";
|
|
4
5
|
import { getBaseUrl } from "./base-url";
|
|
5
6
|
|
|
6
7
|
export const getSitemapBaseUrl = getBaseUrl;
|
|
@@ -26,15 +27,17 @@ export const getMarkdownPath = (baseUrl: string, slug: string | null) => {
|
|
|
26
27
|
: `${baseUrl}/index.md`;
|
|
27
28
|
};
|
|
28
29
|
|
|
30
|
+
// Tagged, not a bare `client.fetch`: an untagged read can't be invalidated.
|
|
29
31
|
export const getSitemap = async () => {
|
|
30
|
-
|
|
31
|
-
|
|
32
|
-
|
|
33
|
-
|
|
34
|
-
|
|
35
|
-
|
|
36
|
-
pinnedIds: PINNED_PAGE_ENTRIES.map((entry) => entry.id),
|
|
32
|
+
// Lazy: the webhook imports `getPagePath` from here, and a static import would
|
|
33
|
+
// make it evaluate `defineLive()` for a string helper.
|
|
34
|
+
const { fetchSanity } = await import("@/lib/integrations/sanity/live");
|
|
35
|
+
|
|
36
|
+
const { data } = await fetchSanity<PAGE_SITEMAP_QUERY_RESULT>({
|
|
37
|
+
query: PAGE_SITEMAP_QUERY,
|
|
38
|
+
params: { pinnedIds: PINNED_PAGE_ENTRIES.map((entry) => entry.id) },
|
|
39
|
+
tags: [SITEMAP_TAG],
|
|
37
40
|
});
|
|
38
41
|
|
|
39
|
-
return
|
|
42
|
+
return data;
|
|
40
43
|
};
|
|
@@ -14,6 +14,15 @@ export function proxy(request: NextRequest) {
|
|
|
14
14
|
return NextResponse.next();
|
|
15
15
|
}
|
|
16
16
|
|
|
17
|
+
// Collapse repeated slashes before any rewrite, or the rewrite below would
|
|
18
|
+
// mask the alias (e.g. /foo//bar.md silently resolving to /foo/bar.md).
|
|
19
|
+
const collapsedPathname = pathname.replace(/\/{2,}/g, "/");
|
|
20
|
+
if (collapsedPathname !== pathname) {
|
|
21
|
+
const url = request.nextUrl.clone();
|
|
22
|
+
url.pathname = collapsedPathname;
|
|
23
|
+
return NextResponse.redirect(url, 308);
|
|
24
|
+
}
|
|
25
|
+
|
|
17
26
|
if (pathname === "/" && acceptHeader.includes("text/markdown")) {
|
|
18
27
|
const url = request.nextUrl.clone();
|
|
19
28
|
url.pathname = "/api/index.md";
|
|
@@ -0,0 +1,112 @@
|
|
|
1
|
+
import { NextResponse } from "next/server";
|
|
2
|
+
import { isSanityConfigured } from "@/lib/integrations/check-integration";
|
|
3
|
+
import {
|
|
4
|
+
getDynamicFetchOptions,
|
|
5
|
+
sanityFetchCached,
|
|
6
|
+
} from "@/lib/integrations/sanity/live";
|
|
7
|
+
import { PAGE_QUERY } from "@/lib/integrations/sanity/queries";
|
|
8
|
+
import type { PAGE_QUERY_RESULT } from "@/lib/integrations/sanity/sanity.types";
|
|
9
|
+
import { renderPageBuilderMarkdown } from "@/lib/integrations/sanity/utils/page-builder-markdown";
|
|
10
|
+
import { getBaseUrl } from "@/lib/utils/base-url";
|
|
11
|
+
import { getSlugTag } from "@/lib/utils/slug-tag";
|
|
12
|
+
|
|
13
|
+
type PageResult = NonNullable<PAGE_QUERY_RESULT>;
|
|
14
|
+
|
|
15
|
+
const isValidSegment = (segment: string) =>
|
|
16
|
+
segment.length > 0 && segment !== "." && segment !== "..";
|
|
17
|
+
|
|
18
|
+
const getSlugPath = (slugParts?: string[]) => {
|
|
19
|
+
if (!slugParts?.length) return null;
|
|
20
|
+
|
|
21
|
+
const lastSegment = slugParts.at(-1);
|
|
22
|
+
|
|
23
|
+
if (!lastSegment?.endsWith(".md")) return null;
|
|
24
|
+
|
|
25
|
+
const pathSegments = [...slugParts];
|
|
26
|
+
pathSegments[pathSegments.length - 1] = lastSegment.slice(0, -3);
|
|
27
|
+
|
|
28
|
+
if (!pathSegments.every(isValidSegment)) return null;
|
|
29
|
+
|
|
30
|
+
const slug = pathSegments.join("/");
|
|
31
|
+
|
|
32
|
+
return slug === "index" ? null : slug;
|
|
33
|
+
};
|
|
34
|
+
|
|
35
|
+
export async function GET(
|
|
36
|
+
_request: Request,
|
|
37
|
+
props: { params: Promise<{ slug?: string[] }> },
|
|
38
|
+
) {
|
|
39
|
+
const { slug: rawSlugParts } = await props.params;
|
|
40
|
+
const slug = getSlugPath(rawSlugParts);
|
|
41
|
+
|
|
42
|
+
if (
|
|
43
|
+
rawSlugParts?.length &&
|
|
44
|
+
slug === null &&
|
|
45
|
+
rawSlugParts.join("/") !== "index.md"
|
|
46
|
+
) {
|
|
47
|
+
return new NextResponse(null, { status: 404 });
|
|
48
|
+
}
|
|
49
|
+
|
|
50
|
+
if (!rawSlugParts?.length) {
|
|
51
|
+
return new NextResponse(null, { status: 404 });
|
|
52
|
+
}
|
|
53
|
+
|
|
54
|
+
if (!isSanityConfigured()) {
|
|
55
|
+
return new NextResponse("# 404 Not Found\n\nCMS not configured.", {
|
|
56
|
+
status: 404,
|
|
57
|
+
headers: { "Content-Type": "text/markdown; charset=utf-8" },
|
|
58
|
+
});
|
|
59
|
+
}
|
|
60
|
+
|
|
61
|
+
const fetchOptions = await getDynamicFetchOptions();
|
|
62
|
+
const { data: page } = await sanityFetchCached<PageResult>({
|
|
63
|
+
query: PAGE_QUERY,
|
|
64
|
+
params: { slug },
|
|
65
|
+
...fetchOptions,
|
|
66
|
+
// This route answers at /api/<slug>.md, which no `revalidatePath` on the
|
|
67
|
+
// page path reaches. The tag is the only thing that clears it.
|
|
68
|
+
tags: [getSlugTag(slug)],
|
|
69
|
+
});
|
|
70
|
+
|
|
71
|
+
const baseUrl = getBaseUrl().replace(/\/$/, "");
|
|
72
|
+
|
|
73
|
+
if (!page) {
|
|
74
|
+
return new NextResponse("# 404 Not Found\n\nPage not found.", {
|
|
75
|
+
status: 404,
|
|
76
|
+
headers: { "Content-Type": "text/markdown; charset=utf-8" },
|
|
77
|
+
});
|
|
78
|
+
}
|
|
79
|
+
|
|
80
|
+
const body = renderPageBuilderMarkdown(page.pageBuilder);
|
|
81
|
+
const canonicalPath = slug ? `/${slug}` : "/";
|
|
82
|
+
|
|
83
|
+
return new NextResponse(
|
|
84
|
+
[
|
|
85
|
+
`# ${page.title}`,
|
|
86
|
+
"",
|
|
87
|
+
page._updatedAt
|
|
88
|
+
? `**Updated:** ${new Date(page._updatedAt).toLocaleDateString()}`
|
|
89
|
+
: null,
|
|
90
|
+
"",
|
|
91
|
+
`Canonical URL: ${baseUrl}${canonicalPath}`,
|
|
92
|
+
"",
|
|
93
|
+
"---",
|
|
94
|
+
"",
|
|
95
|
+
body || "No page builder content yet.",
|
|
96
|
+
"",
|
|
97
|
+
"---",
|
|
98
|
+
"",
|
|
99
|
+
`[View all content](${baseUrl}/sitemap.md)`,
|
|
100
|
+
]
|
|
101
|
+
.filter((part): part is string => part !== null)
|
|
102
|
+
.join("\n"),
|
|
103
|
+
{
|
|
104
|
+
headers: {
|
|
105
|
+
"Content-Type": "text/markdown; charset=utf-8",
|
|
106
|
+
Vary: "Accept",
|
|
107
|
+
"X-Content-Type-Options": "nosniff",
|
|
108
|
+
Link: `<${baseUrl}${canonicalPath}>; rel="canonical"`,
|
|
109
|
+
},
|
|
110
|
+
},
|
|
111
|
+
);
|
|
112
|
+
}
|
package/src/modules/features/cms/sanity-pagebuilder-cache/files/app/api/draft-mode/enable/route.ts
ADDED
|
@@ -0,0 +1,20 @@
|
|
|
1
|
+
import { NextResponse } from "next/server";
|
|
2
|
+
import { defineEnableDraftMode } from "next-sanity/draft-mode";
|
|
3
|
+
import { isSanityConfigured } from "@/lib/integrations/check-integration";
|
|
4
|
+
import { client } from "@/lib/integrations/sanity/client";
|
|
5
|
+
import { sanityToken } from "@/lib/integrations/sanity/env";
|
|
6
|
+
|
|
7
|
+
const draftModeHandler =
|
|
8
|
+
isSanityConfigured() && client
|
|
9
|
+
? defineEnableDraftMode({
|
|
10
|
+
client: client.withConfig({ token: sanityToken }),
|
|
11
|
+
})
|
|
12
|
+
: {
|
|
13
|
+
GET: () =>
|
|
14
|
+
NextResponse.json(
|
|
15
|
+
{ error: "Sanity is not configured" },
|
|
16
|
+
{ status: 503 },
|
|
17
|
+
),
|
|
18
|
+
};
|
|
19
|
+
|
|
20
|
+
export const { GET } = draftModeHandler;
|
|
@@ -0,0 +1,292 @@
|
|
|
1
|
+
import { revalidatePath, revalidateTag } from "next/cache";
|
|
2
|
+
import { type NextRequest, NextResponse } from "next/server";
|
|
3
|
+
import { parseBody } from "next-sanity/webhook";
|
|
4
|
+
// `directClient`, never `client`: a CDN hit returns the pre-mutation slug, which
|
|
5
|
+
// purges the old path twice and never the new one.
|
|
6
|
+
import { directClient } from "@/lib/integrations/sanity/client";
|
|
7
|
+
import { pageBuilderReferenceMembers as pbrm } from "@/lib/integrations/sanity/page-builder-config";
|
|
8
|
+
import {
|
|
9
|
+
ALL_PAGE_SLUGS_QUERY,
|
|
10
|
+
FOLDER_PATH_QUERY,
|
|
11
|
+
P_REF_DOC_PAGES_QUERY,
|
|
12
|
+
P_REV_FOLDER_DESCENDANTS_QUERY,
|
|
13
|
+
P_REV_SLUGS_QUERY,
|
|
14
|
+
} from "@/lib/integrations/sanity/queries";
|
|
15
|
+
import {
|
|
16
|
+
composeDescendantPaths,
|
|
17
|
+
composeResolvedSlug,
|
|
18
|
+
} from "@/lib/integrations/sanity/revalidate-paths";
|
|
19
|
+
import type {
|
|
20
|
+
ALL_PAGE_SLUGS_QUERY_RESULT,
|
|
21
|
+
FOLDER_PATH_QUERY_RESULT,
|
|
22
|
+
P_REF_DOC_PAGES_QUERY_RESULT,
|
|
23
|
+
P_REV_FOLDER_DESCENDANTS_QUERY_RESULT,
|
|
24
|
+
} from "@/lib/integrations/sanity/sanity.types";
|
|
25
|
+
import {
|
|
26
|
+
BLOG_COLLECTION_TAGS,
|
|
27
|
+
BLOG_COLLECTION_TYPES,
|
|
28
|
+
CROSS_DOC_TAG_DEPENDENCIES,
|
|
29
|
+
PURGEABLE_TAGS,
|
|
30
|
+
REFERENCEABLE_TYPES,
|
|
31
|
+
SELF_TAGGED_TYPES,
|
|
32
|
+
} from "@/lib/integrations/sanity/tags";
|
|
33
|
+
import { getPagePath } from "@/lib/utils/sitemap";
|
|
34
|
+
import { getSlugTag } from "@/lib/utils/slug-tag";
|
|
35
|
+
|
|
36
|
+
const ERRORS = {
|
|
37
|
+
BAD_REQ: ["Bad Request", { status: 400 }],
|
|
38
|
+
INVALID_SIG: ["Invalid signature", { status: 401 }],
|
|
39
|
+
} as const;
|
|
40
|
+
|
|
41
|
+
type WebhookSlug = {
|
|
42
|
+
current?: string | null;
|
|
43
|
+
};
|
|
44
|
+
|
|
45
|
+
type WebhookReference = {
|
|
46
|
+
_ref?: string | null;
|
|
47
|
+
};
|
|
48
|
+
|
|
49
|
+
type WebhookDocument = {
|
|
50
|
+
_id: string;
|
|
51
|
+
_type: string;
|
|
52
|
+
slug?: WebhookSlug | null;
|
|
53
|
+
pageFolder?: WebhookReference | null;
|
|
54
|
+
parentFolder?: WebhookReference | null;
|
|
55
|
+
};
|
|
56
|
+
|
|
57
|
+
type MutationOperation = "create" | "update" | "delete";
|
|
58
|
+
|
|
59
|
+
type RevalidateWebhookBody = WebhookDocument & {
|
|
60
|
+
operation?: MutationOperation;
|
|
61
|
+
purgeAll?: boolean;
|
|
62
|
+
before?: Partial<WebhookDocument> | null;
|
|
63
|
+
after?: Partial<WebhookDocument> | null;
|
|
64
|
+
};
|
|
65
|
+
|
|
66
|
+
const referenceableTypes = new Set<string>([
|
|
67
|
+
...pbrm.map((m) => m.documentType),
|
|
68
|
+
...REFERENCEABLE_TYPES,
|
|
69
|
+
]);
|
|
70
|
+
|
|
71
|
+
// Not `"max"`: a named profile expires a year out, so Next serves the stale entry
|
|
72
|
+
// once while refreshing. A publish should land on the next request.
|
|
73
|
+
const PURGE = { expire: 0 } as const;
|
|
74
|
+
|
|
75
|
+
const bustTags = (tags: readonly string[]) => {
|
|
76
|
+
for (const tag of tags) {
|
|
77
|
+
revalidateTag(tag, PURGE);
|
|
78
|
+
}
|
|
79
|
+
};
|
|
80
|
+
|
|
81
|
+
// Both: the path clears the page's route, the tag clears what no page render
|
|
82
|
+
// touches — above all the markdown route at /api/<slug>.md.
|
|
83
|
+
const bustPage = (slug: string | null) => {
|
|
84
|
+
revalidatePath(getPagePath(slug));
|
|
85
|
+
revalidateTag(getSlugTag(slug), PURGE);
|
|
86
|
+
};
|
|
87
|
+
|
|
88
|
+
const normalizeRevalidationSlug = (slug: string | null | undefined) => {
|
|
89
|
+
if (slug === undefined) return null;
|
|
90
|
+
if (slug === null) return "/";
|
|
91
|
+
return `/${slug}`;
|
|
92
|
+
};
|
|
93
|
+
|
|
94
|
+
const getCanonicalPageSlug = async (documentId: string) => {
|
|
95
|
+
if (!directClient) return null;
|
|
96
|
+
|
|
97
|
+
const pageSlugs = await directClient.fetch<Array<{ slug: string | null }>>(
|
|
98
|
+
P_REV_SLUGS_QUERY,
|
|
99
|
+
{ documentIds: [documentId] },
|
|
100
|
+
);
|
|
101
|
+
|
|
102
|
+
return normalizeRevalidationSlug(
|
|
103
|
+
pageSlugs.find((page) => page.slug !== undefined)?.slug,
|
|
104
|
+
);
|
|
105
|
+
};
|
|
106
|
+
|
|
107
|
+
// Homepage comes back as `null`, as `bustPage` expects.
|
|
108
|
+
const getAllPageSlugs = async () => {
|
|
109
|
+
if (!directClient) return [];
|
|
110
|
+
|
|
111
|
+
const pages =
|
|
112
|
+
await directClient.fetch<ALL_PAGE_SLUGS_QUERY_RESULT>(ALL_PAGE_SLUGS_QUERY);
|
|
113
|
+
|
|
114
|
+
return [
|
|
115
|
+
null,
|
|
116
|
+
...(pages ?? []).flatMap((page) =>
|
|
117
|
+
page.slug && page.slug !== "/" ? [`/${page.slug}`] : [],
|
|
118
|
+
),
|
|
119
|
+
];
|
|
120
|
+
};
|
|
121
|
+
|
|
122
|
+
const getFolder = async (folderId: string | null | undefined) => {
|
|
123
|
+
if (!(directClient && folderId)) return null;
|
|
124
|
+
|
|
125
|
+
const folder = await directClient.fetch<FOLDER_PATH_QUERY_RESULT>(
|
|
126
|
+
FOLDER_PATH_QUERY,
|
|
127
|
+
{ folderId },
|
|
128
|
+
);
|
|
129
|
+
|
|
130
|
+
return folder ?? null;
|
|
131
|
+
};
|
|
132
|
+
|
|
133
|
+
const getPagesReferencing = async (documentIds: readonly string[]) => {
|
|
134
|
+
if (!(directClient && documentIds.length)) return [];
|
|
135
|
+
|
|
136
|
+
const pages = await directClient.fetch<P_REF_DOC_PAGES_QUERY_RESULT>(
|
|
137
|
+
P_REF_DOC_PAGES_QUERY,
|
|
138
|
+
{ documentIds },
|
|
139
|
+
);
|
|
140
|
+
|
|
141
|
+
return (pages ?? []).map((page) => normalizeRevalidationSlug(page.slug));
|
|
142
|
+
};
|
|
143
|
+
|
|
144
|
+
// `slug.current` is only the page's own segment, so the URL is rebuilt from the
|
|
145
|
+
// folder. `undefined` means "no such revision", distinct from the `null` a root
|
|
146
|
+
// page resolves to — without that split, a missing `before()` purges the homepage.
|
|
147
|
+
const resolvePageSlug = async (
|
|
148
|
+
revision: Partial<WebhookDocument> | null | undefined,
|
|
149
|
+
) => {
|
|
150
|
+
if (!revision) return undefined;
|
|
151
|
+
|
|
152
|
+
const folder = await getFolder(revision.pageFolder?._ref);
|
|
153
|
+
|
|
154
|
+
return composeResolvedSlug(folder?.path ?? null, revision.slug?.current);
|
|
155
|
+
};
|
|
156
|
+
|
|
157
|
+
// A folder move rewrites every URL beneath it, and none of those are `page`
|
|
158
|
+
// mutations, so nothing else here would touch them.
|
|
159
|
+
const revalidateFolderSubtree = async (body: RevalidateWebhookBody) => {
|
|
160
|
+
const { before, after } = body;
|
|
161
|
+
|
|
162
|
+
const previousFolderSlug = before?.slug?.current;
|
|
163
|
+
const moved =
|
|
164
|
+
previousFolderSlug !== after?.slug?.current ||
|
|
165
|
+
before?.parentFolder?._ref !== after?.parentFolder?._ref;
|
|
166
|
+
|
|
167
|
+
// A title-only edit changes no URL. Deletion is skipped outright: these are hard
|
|
168
|
+
// references, so Sanity refuses to delete a folder that still has descendants.
|
|
169
|
+
if (body.operation === "delete") return [];
|
|
170
|
+
if (!(directClient && moved && previousFolderSlug)) return [];
|
|
171
|
+
|
|
172
|
+
const previousParent = await getFolder(before?.parentFolder?._ref);
|
|
173
|
+
|
|
174
|
+
const descendants =
|
|
175
|
+
await directClient.fetch<P_REV_FOLDER_DESCENDANTS_QUERY_RESULT>(
|
|
176
|
+
P_REV_FOLDER_DESCENDANTS_QUERY,
|
|
177
|
+
{ folderId: body._id },
|
|
178
|
+
);
|
|
179
|
+
|
|
180
|
+
const movedIds: string[] = [];
|
|
181
|
+
|
|
182
|
+
for (const descendant of descendants ?? []) {
|
|
183
|
+
const paths = composeDescendantPaths({
|
|
184
|
+
folderChain: descendant.folderChain ?? [],
|
|
185
|
+
ownSlug: descendant.ownSlug,
|
|
186
|
+
folderId: body._id,
|
|
187
|
+
previousFolderSlug,
|
|
188
|
+
previousParentPath: previousParent?.path ?? null,
|
|
189
|
+
});
|
|
190
|
+
|
|
191
|
+
if (!paths) {
|
|
192
|
+
console.error("Revalidation: unresolvable folder chain", descendant._id);
|
|
193
|
+
continue;
|
|
194
|
+
}
|
|
195
|
+
|
|
196
|
+
movedIds.push(descendant._id);
|
|
197
|
+
bustPage(paths.current);
|
|
198
|
+
bustPage(paths.previous);
|
|
199
|
+
}
|
|
200
|
+
|
|
201
|
+
// Pages linking into the subtree inline the old slugs too.
|
|
202
|
+
return getPagesReferencing(movedIds);
|
|
203
|
+
};
|
|
204
|
+
|
|
205
|
+
const getAffectedPageSlugsForWebhook = async (body: RevalidateWebhookBody) => {
|
|
206
|
+
const pages: Array<string | null> = [];
|
|
207
|
+
|
|
208
|
+
if (body._type === "page" && body.before) {
|
|
209
|
+
const previousSlug = await resolvePageSlug(body.before);
|
|
210
|
+
|
|
211
|
+
// Resolved, not raw: a moved page keeps its own slug but vacates its URL.
|
|
212
|
+
const vacated =
|
|
213
|
+
body.operation === "delete" ||
|
|
214
|
+
(body.operation === "update" &&
|
|
215
|
+
previousSlug !== (await resolvePageSlug(body.after)));
|
|
216
|
+
|
|
217
|
+
if (vacated && previousSlug !== undefined) bustPage(previousSlug);
|
|
218
|
+
}
|
|
219
|
+
|
|
220
|
+
// If the document is a page, get its slug
|
|
221
|
+
if (body._type === "page") {
|
|
222
|
+
const data = await getCanonicalPageSlug(body._id);
|
|
223
|
+
if (data !== null) pages.push(data);
|
|
224
|
+
}
|
|
225
|
+
|
|
226
|
+
// Page-builder blocks, `author`, `blogCategory` and other pages are all
|
|
227
|
+
// inlined into pages that no other branch here revalidates.
|
|
228
|
+
if (referenceableTypes.has(body._type)) {
|
|
229
|
+
pages.push(...(await getPagesReferencing([body._id])));
|
|
230
|
+
}
|
|
231
|
+
|
|
232
|
+
if (body._type === "pageFolder") {
|
|
233
|
+
pages.push(...(await revalidateFolderSubtree(body)));
|
|
234
|
+
}
|
|
235
|
+
|
|
236
|
+
return [...new Set(pages.filter((page): page is string => page !== null))];
|
|
237
|
+
};
|
|
238
|
+
|
|
239
|
+
export async function POST(request: NextRequest) {
|
|
240
|
+
try {
|
|
241
|
+
const { body, isValidSignature } = await parseBody<RevalidateWebhookBody>(
|
|
242
|
+
request,
|
|
243
|
+
process.env.SANITY_REVALIDATE_SECRET,
|
|
244
|
+
);
|
|
245
|
+
|
|
246
|
+
if (!isValidSignature) return new Response(...ERRORS.INVALID_SIG);
|
|
247
|
+
|
|
248
|
+
// Ahead of the `_type`/`_id` guard: a purge payload carries neither. The hatch
|
|
249
|
+
// for a missed `cacheTag`, which the ~1y cacheLife would otherwise freeze.
|
|
250
|
+
if (body?.purgeAll) {
|
|
251
|
+
// Enumerated here so PURGEABLE_TAGS stays query-free. Without this the hatch
|
|
252
|
+
// never reached page entries or /api/<slug>.md.
|
|
253
|
+
const slugs = await getAllPageSlugs();
|
|
254
|
+
|
|
255
|
+
bustTags(PURGEABLE_TAGS);
|
|
256
|
+
for (const slug of slugs) bustPage(slug);
|
|
257
|
+
|
|
258
|
+
return NextResponse.json({
|
|
259
|
+
status: 200,
|
|
260
|
+
now: Date.now(),
|
|
261
|
+
purged: PURGEABLE_TAGS.length + slugs.length,
|
|
262
|
+
pages: slugs.length,
|
|
263
|
+
});
|
|
264
|
+
}
|
|
265
|
+
|
|
266
|
+
if (!(body?._type && body._id)) return new Response(...ERRORS.BAD_REQ);
|
|
267
|
+
if (body._id.startsWith("drafts."))
|
|
268
|
+
return NextResponse.json({ status: 200, now: Date.now(), skipped: true });
|
|
269
|
+
|
|
270
|
+
const affectedPageSlugs = await getAffectedPageSlugsForWebhook(body);
|
|
271
|
+
|
|
272
|
+
for (const slug of affectedPageSlugs) {
|
|
273
|
+
bustPage(slug);
|
|
274
|
+
}
|
|
275
|
+
|
|
276
|
+
// Layout singletons and redirects are tagged with their own `_type`.
|
|
277
|
+
if (SELF_TAGGED_TYPES.has(body._type)) revalidateTag(body._type, PURGE);
|
|
278
|
+
|
|
279
|
+
// Queries that read across document types — a `structuredData` entry that
|
|
280
|
+
// folds in `companyData`, a navbar that derefs pages.
|
|
281
|
+
bustTags(CROSS_DOC_TAG_DEPENDENCIES[body._type] ?? []);
|
|
282
|
+
|
|
283
|
+
// The content-collection block caches list/count/categories under their own
|
|
284
|
+
// tags, which no per-page revalidation above touches.
|
|
285
|
+
if (BLOG_COLLECTION_TYPES.has(body._type)) bustTags(BLOG_COLLECTION_TAGS);
|
|
286
|
+
|
|
287
|
+
return NextResponse.json({ status: 200, now: Date.now() });
|
|
288
|
+
} catch (error) {
|
|
289
|
+
console.error("Revalidation error:", error);
|
|
290
|
+
return new Response("Internal Server Error", { status: 500 });
|
|
291
|
+
}
|
|
292
|
+
}
|