bsmnt 0.10.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.
Files changed (321) hide show
  1. package/dist/application/add-integration/index.js.map +1 -1
  2. package/dist/application/shared/node-version.js.map +1 -1
  3. package/dist/core/create/internal-ids.d.ts +3 -0
  4. package/dist/core/create/internal-ids.d.ts.map +1 -1
  5. package/dist/core/create/internal-ids.js +4 -0
  6. package/dist/core/create/internal-ids.js.map +1 -1
  7. package/dist/domain/agents.d.ts.map +1 -1
  8. package/dist/domain/animations.d.ts.map +1 -1
  9. package/dist/domain/cms.d.ts.map +1 -1
  10. package/dist/domain/forms.d.ts.map +1 -1
  11. package/dist/domain/hooks.d.ts.map +1 -1
  12. package/dist/domain/skills.d.ts.map +1 -1
  13. package/dist/domain/templates.d.ts +10 -0
  14. package/dist/domain/templates.d.ts.map +1 -1
  15. package/dist/domain/templates.js +5 -0
  16. package/dist/domain/templates.js.map +1 -1
  17. package/dist/index.js +18 -5
  18. package/dist/index.js.map +1 -1
  19. package/dist/infrastructure/agents/register-sanity-mcp.js +3 -8
  20. package/dist/infrastructure/agents/register-sanity-mcp.js.map +1 -1
  21. package/dist/infrastructure/agents/register-storybook-mcp.d.ts +18 -0
  22. package/dist/infrastructure/agents/register-storybook-mcp.d.ts.map +1 -0
  23. package/dist/infrastructure/agents/register-storybook-mcp.js +157 -0
  24. package/dist/infrastructure/agents/register-storybook-mcp.js.map +1 -0
  25. package/dist/infrastructure/create/lifecycle-registry.d.ts.map +1 -1
  26. package/dist/infrastructure/create/lifecycle-registry.js +39 -1
  27. package/dist/infrastructure/create/lifecycle-registry.js.map +1 -1
  28. package/dist/infrastructure/create/merge-registry.d.ts.map +1 -1
  29. package/dist/infrastructure/create/merge-registry.js +4 -0
  30. package/dist/infrastructure/create/merge-registry.js.map +1 -1
  31. package/dist/infrastructure/create/sanity-auth.js.map +1 -1
  32. package/dist/infrastructure/create/setup-agent.d.ts +6 -1
  33. package/dist/infrastructure/create/setup-agent.d.ts.map +1 -1
  34. package/dist/infrastructure/create/setup-agent.js +23 -21
  35. package/dist/infrastructure/create/setup-agent.js.map +1 -1
  36. package/dist/infrastructure/create/setup-dotenvx.js.map +1 -1
  37. package/dist/infrastructure/create/setup-remote.js.map +1 -1
  38. package/dist/infrastructure/create/setup-sanity.d.ts.map +1 -1
  39. package/dist/infrastructure/create/setup-sanity.js +12 -1
  40. package/dist/infrastructure/create/setup-sanity.js.map +1 -1
  41. package/dist/infrastructure/integrations/inject-integration.d.ts.map +1 -1
  42. package/dist/infrastructure/integrations/inject-integration.js +2 -0
  43. package/dist/infrastructure/integrations/inject-integration.js.map +1 -1
  44. package/dist/infrastructure/update/notifier.js.map +1 -1
  45. package/dist/modules/features/cms/index.d.ts.map +1 -1
  46. package/dist/modules/features/cms/index.js +2 -0
  47. package/dist/modules/features/cms/index.js.map +1 -1
  48. package/dist/modules/features/cms/sanity/config.d.ts.map +1 -1
  49. package/dist/modules/features/cms/sanity/config.js +1 -0
  50. package/dist/modules/features/cms/sanity/config.js.map +1 -1
  51. package/dist/modules/features/cms/sanity/index.d.ts.map +1 -1
  52. package/dist/modules/features/cms/sanity/index.js +10 -1
  53. package/dist/modules/features/cms/sanity/index.js.map +1 -1
  54. package/dist/modules/features/cms/sanity/mergers/check-integration.d.ts.map +1 -1
  55. package/dist/modules/features/cms/sanity/mergers/check-integration.js +15 -6
  56. package/dist/modules/features/cms/sanity/mergers/check-integration.js.map +1 -1
  57. package/dist/modules/features/cms/sanity/mergers/knip.d.ts.map +1 -1
  58. package/dist/modules/features/cms/sanity/mergers/knip.js +1 -0
  59. package/dist/modules/features/cms/sanity/mergers/knip.js.map +1 -1
  60. package/dist/modules/features/cms/sanity/mergers/layout.js.map +1 -1
  61. package/dist/modules/features/cms/sanity/mergers/package-json.d.ts +12 -0
  62. package/dist/modules/features/cms/sanity/mergers/package-json.d.ts.map +1 -0
  63. package/dist/modules/features/cms/sanity/mergers/package-json.js +19 -0
  64. package/dist/modules/features/cms/sanity/mergers/package-json.js.map +1 -0
  65. package/dist/modules/features/cms/sanity-pagebuilder/index.d.ts.map +1 -1
  66. package/dist/modules/features/cms/sanity-pagebuilder/index.js +7 -1
  67. package/dist/modules/features/cms/sanity-pagebuilder/index.js.map +1 -1
  68. package/dist/modules/features/cms/sanity-pagebuilder-cache/config.d.ts +14 -0
  69. package/dist/modules/features/cms/sanity-pagebuilder-cache/config.d.ts.map +1 -0
  70. package/dist/modules/features/cms/sanity-pagebuilder-cache/config.js +50 -0
  71. package/dist/modules/features/cms/sanity-pagebuilder-cache/config.js.map +1 -0
  72. package/dist/modules/features/cms/sanity-pagebuilder-cache/index.d.ts +3 -0
  73. package/dist/modules/features/cms/sanity-pagebuilder-cache/index.d.ts.map +1 -0
  74. package/dist/modules/features/cms/sanity-pagebuilder-cache/index.js +55 -0
  75. package/dist/modules/features/cms/sanity-pagebuilder-cache/index.js.map +1 -0
  76. package/dist/modules/presets/index.d.ts.map +1 -1
  77. package/dist/modules/presets/index.js +2 -0
  78. package/dist/modules/presets/index.js.map +1 -1
  79. package/dist/modules/presets/next-pagebuilder-cache.d.ts +3 -0
  80. package/dist/modules/presets/next-pagebuilder-cache.d.ts.map +1 -0
  81. package/dist/modules/presets/next-pagebuilder-cache.js +10 -0
  82. package/dist/modules/presets/next-pagebuilder-cache.js.map +1 -0
  83. package/dist/paths.d.ts +6 -0
  84. package/dist/paths.d.ts.map +1 -1
  85. package/dist/paths.js +6 -0
  86. package/dist/paths.js.map +1 -1
  87. package/package.json +5 -2
  88. package/src/modules/features/cms/sanity/files/lib/integrations/check-integration.ts +7 -6
  89. package/src/modules/features/cms/sanity/files/lib/integrations/sanity/README.md +1 -1
  90. package/src/modules/features/cms/sanity/files/lib/integrations/sanity/components/rich-text.tsx +1 -0
  91. package/src/modules/features/cms/sanity/files/lib/integrations/sanity/env.ts +2 -2
  92. package/src/modules/features/cms/sanity-pagebuilder/files/app/api/[[...slug]]/route.ts +4 -0
  93. package/src/modules/features/cms/sanity-pagebuilder/files/app/api/revalidate/route.ts +172 -24
  94. package/src/modules/features/cms/sanity-pagebuilder/files/app/feed.xml/route.ts +14 -7
  95. package/src/modules/features/cms/sanity-pagebuilder/files/app/sitemap.md/route.ts +2 -2
  96. package/src/modules/features/cms/sanity-pagebuilder/files/app/studio/[[...tool]]/page.tsx +16 -1
  97. package/src/modules/features/cms/sanity-pagebuilder/files/components/sanity/rich-text.tsx +1 -0
  98. package/src/modules/features/cms/sanity-pagebuilder/files/components/sanity/visual-editing.tsx +2 -2
  99. package/src/modules/features/cms/sanity-pagebuilder/files/lib/integrations/check-integration.ts +6 -5
  100. package/src/modules/features/cms/sanity-pagebuilder/files/lib/integrations/sanity/client.ts +8 -0
  101. package/src/modules/features/cms/sanity-pagebuilder/files/lib/integrations/sanity/components/slug-notices.tsx +6 -16
  102. package/src/modules/features/cms/sanity-pagebuilder/files/lib/integrations/sanity/env.ts +2 -2
  103. package/src/modules/features/cms/sanity-pagebuilder/files/lib/integrations/sanity/fetchers/layout.ts +22 -52
  104. package/src/modules/features/cms/sanity-pagebuilder/files/lib/integrations/sanity/icons.ts +2 -2
  105. package/src/modules/features/cms/sanity-pagebuilder/files/lib/integrations/sanity/live/index.tsx +3 -1
  106. package/src/modules/features/cms/sanity-pagebuilder/files/lib/integrations/sanity/page-builder-config.ts +7 -0
  107. package/src/modules/features/cms/sanity-pagebuilder/files/lib/integrations/sanity/page-tree-pane.tsx +70 -57
  108. package/src/modules/features/cms/sanity-pagebuilder/files/lib/integrations/sanity/queries.ts +119 -29
  109. package/src/modules/features/cms/sanity-pagebuilder/files/lib/integrations/sanity/revalidate-paths.ts +70 -0
  110. package/src/modules/features/cms/sanity-pagebuilder/files/lib/integrations/sanity/schemas/components/index.ts +1 -0
  111. package/src/modules/features/cms/sanity-pagebuilder/files/lib/integrations/sanity/schemas/components/reusable/rich-text-block.ts +39 -0
  112. package/src/modules/features/cms/sanity-pagebuilder/files/lib/integrations/sanity/schemas/index.ts +2 -0
  113. package/src/modules/features/cms/sanity-pagebuilder/files/lib/integrations/sanity/schemas/shared/page.ts +12 -0
  114. package/src/modules/features/cms/sanity-pagebuilder/files/lib/integrations/sanity/tags.ts +56 -0
  115. package/src/modules/features/cms/sanity-pagebuilder/files/lib/integrations/sanity/utils/page-builder-markdown.ts +12 -0
  116. package/src/modules/features/cms/sanity-pagebuilder/files/lib/utils/sitemap.ts +11 -8
  117. package/src/modules/features/cms/sanity-pagebuilder-cache/files/.knip.json +8 -0
  118. package/src/modules/features/cms/sanity-pagebuilder-cache/files/app/api/[[...slug]]/route.ts +112 -0
  119. package/src/modules/features/cms/sanity-pagebuilder-cache/files/app/api/draft-mode/disable/route.ts +7 -0
  120. package/src/modules/features/cms/sanity-pagebuilder-cache/files/app/api/draft-mode/enable/route.ts +20 -0
  121. package/src/modules/features/cms/sanity-pagebuilder-cache/files/app/api/revalidate/route.ts +292 -0
  122. package/src/modules/features/cms/sanity-pagebuilder-cache/files/app/feed.xml/route.ts +66 -0
  123. package/src/modules/features/cms/sanity-pagebuilder-cache/files/app/sitemap.md/route.ts +132 -0
  124. package/src/modules/features/cms/sanity-pagebuilder-cache/files/app/studio/[[...tool]]/layout.tsx +26 -0
  125. package/src/modules/features/cms/sanity-pagebuilder-cache/files/app/studio/[[...tool]]/page.tsx +23 -0
  126. package/src/modules/features/cms/sanity-pagebuilder-cache/files/components/sanity/draft-mode-toggle.tsx +27 -0
  127. package/src/modules/features/cms/sanity-pagebuilder-cache/files/components/sanity/rich-text.tsx +88 -0
  128. package/src/modules/features/cms/sanity-pagebuilder-cache/files/components/sanity/visual-editing.tsx +11 -0
  129. package/src/modules/features/cms/sanity-pagebuilder-cache/files/components/ui/sanity-image/index.tsx +51 -0
  130. package/src/modules/features/cms/sanity-pagebuilder-cache/files/lib/integrations/check-integration.ts +6 -0
  131. package/src/modules/features/cms/sanity-pagebuilder-cache/files/lib/integrations/sanity/client.ts +37 -0
  132. package/src/modules/features/cms/sanity-pagebuilder-cache/files/lib/integrations/sanity/components/block-actions-item.tsx +81 -0
  133. package/src/modules/features/cms/sanity-pagebuilder-cache/files/lib/integrations/sanity/components/disable-draft-mode.tsx +23 -0
  134. package/src/modules/features/cms/sanity-pagebuilder-cache/files/lib/integrations/sanity/components/page-builder-input.tsx +39 -0
  135. package/src/modules/features/cms/sanity-pagebuilder-cache/files/lib/integrations/sanity/components/page-category-input.tsx +50 -0
  136. package/src/modules/features/cms/sanity-pagebuilder-cache/files/lib/integrations/sanity/components/same-as-input.tsx +66 -0
  137. package/src/modules/features/cms/sanity-pagebuilder-cache/files/lib/integrations/sanity/components/slug-field.tsx +24 -0
  138. package/src/modules/features/cms/sanity-pagebuilder-cache/files/lib/integrations/sanity/components/slug-input.tsx +14 -0
  139. package/src/modules/features/cms/sanity-pagebuilder-cache/files/lib/integrations/sanity/components/slug-notices.tsx +125 -0
  140. package/src/modules/features/cms/sanity-pagebuilder-cache/files/lib/integrations/sanity/confirm-publish-action.ts +40 -0
  141. package/src/modules/features/cms/sanity-pagebuilder-cache/files/lib/integrations/sanity/env.ts +34 -0
  142. package/src/modules/features/cms/sanity-pagebuilder-cache/files/lib/integrations/sanity/fetchers/layout.ts +39 -0
  143. package/src/modules/features/cms/sanity-pagebuilder-cache/files/lib/integrations/sanity/fetchers/redirects.ts +22 -0
  144. package/src/modules/features/cms/sanity-pagebuilder-cache/files/lib/integrations/sanity/icons.ts +81 -0
  145. package/src/modules/features/cms/sanity-pagebuilder-cache/files/lib/integrations/sanity/live/index.tsx +140 -0
  146. package/src/modules/features/cms/sanity-pagebuilder-cache/files/lib/integrations/sanity/markdown-proxy.config.ts +50 -0
  147. package/src/modules/features/cms/sanity-pagebuilder-cache/files/lib/integrations/sanity/page-builder-config.ts +144 -0
  148. package/src/modules/features/cms/sanity-pagebuilder-cache/files/lib/integrations/sanity/page-category.ts +28 -0
  149. package/src/modules/features/cms/sanity-pagebuilder-cache/files/lib/integrations/sanity/page-tree-pane.tsx +431 -0
  150. package/src/modules/features/cms/sanity-pagebuilder-cache/files/lib/integrations/sanity/pinned-pages.ts +54 -0
  151. package/src/modules/features/cms/sanity-pagebuilder-cache/files/lib/integrations/sanity/presentation.ts +119 -0
  152. package/src/modules/features/cms/sanity-pagebuilder-cache/files/lib/integrations/sanity/queries.ts +548 -0
  153. package/src/modules/features/cms/sanity-pagebuilder-cache/files/lib/integrations/sanity/revalidate-paths.ts +70 -0
  154. package/src/modules/features/cms/sanity-pagebuilder-cache/files/lib/integrations/sanity/sanity.cli.ts +29 -0
  155. package/src/modules/features/cms/sanity-pagebuilder-cache/files/lib/integrations/sanity/sanity.config.ts +116 -0
  156. package/src/modules/features/cms/sanity-pagebuilder-cache/files/lib/integrations/sanity/schemas/components/index.ts +6 -0
  157. package/src/modules/features/cms/sanity-pagebuilder-cache/files/lib/integrations/sanity/schemas/components/reusable/blog-content.ts +89 -0
  158. package/src/modules/features/cms/sanity-pagebuilder-cache/files/lib/integrations/sanity/schemas/components/reusable/description.ts +29 -0
  159. package/src/modules/features/cms/sanity-pagebuilder-cache/files/lib/integrations/sanity/schemas/components/reusable/gated-content.ts +178 -0
  160. package/src/modules/features/cms/sanity-pagebuilder-cache/files/lib/integrations/sanity/schemas/components/reusable/hero.ts +28 -0
  161. package/src/modules/features/cms/sanity-pagebuilder-cache/files/lib/integrations/sanity/schemas/components/reusable/rich-text-block.ts +39 -0
  162. package/src/modules/features/cms/sanity-pagebuilder-cache/files/lib/integrations/sanity/schemas/components/singleton/content-collection.ts +45 -0
  163. package/src/modules/features/cms/sanity-pagebuilder-cache/files/lib/integrations/sanity/schemas/content/author.ts +70 -0
  164. package/src/modules/features/cms/sanity-pagebuilder-cache/files/lib/integrations/sanity/schemas/content/blog-category.ts +55 -0
  165. package/src/modules/features/cms/sanity-pagebuilder-cache/files/lib/integrations/sanity/schemas/index.ts +134 -0
  166. package/src/modules/features/cms/sanity-pagebuilder-cache/files/lib/integrations/sanity/schemas/layout/company-data.ts +70 -0
  167. package/src/modules/features/cms/sanity-pagebuilder-cache/files/lib/integrations/sanity/schemas/layout/footer.ts +79 -0
  168. package/src/modules/features/cms/sanity-pagebuilder-cache/files/lib/integrations/sanity/schemas/layout/navbar.ts +75 -0
  169. package/src/modules/features/cms/sanity-pagebuilder-cache/files/lib/integrations/sanity/schemas/layout/redirect.ts +152 -0
  170. package/src/modules/features/cms/sanity-pagebuilder-cache/files/lib/integrations/sanity/schemas/layout/structured-data.ts +124 -0
  171. package/src/modules/features/cms/sanity-pagebuilder-cache/files/lib/integrations/sanity/schemas/shared/link.ts +125 -0
  172. package/src/modules/features/cms/sanity-pagebuilder-cache/files/lib/integrations/sanity/schemas/shared/logo-field.ts +9 -0
  173. package/src/modules/features/cms/sanity-pagebuilder-cache/files/lib/integrations/sanity/schemas/shared/metadata.ts +132 -0
  174. package/src/modules/features/cms/sanity-pagebuilder-cache/files/lib/integrations/sanity/schemas/shared/nav-objects.ts +192 -0
  175. package/src/modules/features/cms/sanity-pagebuilder-cache/files/lib/integrations/sanity/schemas/shared/page-builder.ts +43 -0
  176. package/src/modules/features/cms/sanity-pagebuilder-cache/files/lib/integrations/sanity/schemas/shared/page-folder.ts +124 -0
  177. package/src/modules/features/cms/sanity-pagebuilder-cache/files/lib/integrations/sanity/schemas/shared/page.ts +265 -0
  178. package/src/modules/features/cms/sanity-pagebuilder-cache/files/lib/integrations/sanity/schemas/shared/richText.ts +63 -0
  179. package/src/modules/features/cms/sanity-pagebuilder-cache/files/lib/integrations/sanity/schemas/shared/structured-data-blocks.ts +436 -0
  180. package/src/modules/features/cms/sanity-pagebuilder-cache/files/lib/integrations/sanity/singletons.ts +51 -0
  181. package/src/modules/features/cms/sanity-pagebuilder-cache/files/lib/integrations/sanity/structure.ts +334 -0
  182. package/src/modules/features/cms/sanity-pagebuilder-cache/files/lib/integrations/sanity/tags.ts +89 -0
  183. package/src/modules/features/cms/sanity-pagebuilder-cache/files/lib/integrations/sanity/utils/image.ts +8 -0
  184. package/src/modules/features/cms/sanity-pagebuilder-cache/files/lib/integrations/sanity/utils/link.ts +140 -0
  185. package/src/modules/features/cms/sanity-pagebuilder-cache/files/lib/integrations/sanity/utils/page-builder-markdown.ts +113 -0
  186. package/src/modules/features/cms/sanity-pagebuilder-cache/files/lib/integrations/sanity/utils/page-json-ld.ts +208 -0
  187. package/src/modules/features/cms/sanity-pagebuilder-cache/files/lib/scripts/sanity-typegen.ts +36 -0
  188. package/src/modules/features/cms/sanity-pagebuilder-cache/files/lib/utils/check-redirect.ts +30 -0
  189. package/src/modules/features/cms/sanity-pagebuilder-cache/files/lib/utils/json-ld.tsx +522 -0
  190. package/src/modules/features/cms/sanity-pagebuilder-cache/files/lib/utils/metadata.ts +189 -0
  191. package/src/modules/features/cms/sanity-pagebuilder-cache/files/lib/utils/rss.ts +96 -0
  192. package/src/modules/features/cms/sanity-pagebuilder-cache/files/lib/utils/sitemap.ts +58 -0
  193. package/src/modules/features/cms/sanity-pagebuilder-cache/files/proxy.ts +90 -0
  194. package/src/modules/features/env/dotenvx/files/vercelignore +5 -0
  195. package/src/templates/next-default/.env.example +2 -2
  196. package/src/templates/next-default/.github/workflows/ci.yml +1 -1
  197. package/src/templates/next-default/.vscode/settings.json +0 -2
  198. package/src/templates/next-default/components/ui/image/index.tsx +2 -4
  199. package/src/templates/next-default/knip.json +1 -1
  200. package/src/templates/next-default/lefthook.yml +1 -1
  201. package/src/templates/next-default/lib/utils/README.md +18 -5
  202. package/src/templates/next-default/lib/utils/image-sizes.ts +72 -0
  203. package/src/templates/next-default/lib/utils/merge-refs.ts +56 -0
  204. package/src/templates/next-default/lib/utils/strings.ts +1 -51
  205. package/src/templates/next-default/next.config.ts +2 -3
  206. package/src/templates/next-default/package.json +2 -5
  207. package/src/templates/next-experiments/.env.example +2 -2
  208. package/src/templates/next-experiments/.github/workflows/ci.yml +1 -1
  209. package/src/templates/next-experiments/.vscode/settings.json +0 -2
  210. package/src/templates/next-experiments/app/page.tsx +1 -1
  211. package/src/templates/next-experiments/components/ui/image/index.tsx +2 -4
  212. package/src/templates/next-experiments/knip.json +1 -1
  213. package/src/templates/next-experiments/lefthook.yml +1 -1
  214. package/src/templates/next-experiments/lib/utils/README.md +18 -5
  215. package/src/templates/next-experiments/lib/utils/image-sizes.ts +72 -0
  216. package/src/templates/next-experiments/lib/utils/merge-refs.ts +56 -0
  217. package/src/templates/next-experiments/lib/utils/strings.ts +1 -51
  218. package/src/templates/next-experiments/next.config.ts +2 -3
  219. package/src/templates/next-experiments/package.json +2 -5
  220. package/src/templates/next-pagebuilder/.env.example +1 -0
  221. package/src/templates/next-pagebuilder/.github/workflows/ci.yml +1 -1
  222. package/src/templates/next-pagebuilder/.vscode/settings.json +0 -2
  223. package/src/templates/next-pagebuilder/README.md +64 -0
  224. package/src/templates/next-pagebuilder/app/sitemap.xml/route.ts +3 -1
  225. package/src/templates/next-pagebuilder/biome.json +2 -1
  226. package/src/templates/next-pagebuilder/components/layout/footer/index.tsx +3 -6
  227. package/src/templates/next-pagebuilder/components/layout/header/index.tsx +3 -2
  228. package/src/templates/next-pagebuilder/components/layout/json-ld/index.tsx +3 -8
  229. package/src/templates/next-pagebuilder/components/page-builder/components/content-collection/content-filters.tsx +23 -40
  230. package/src/templates/next-pagebuilder/components/page-builder/components/content-collection/index.tsx +88 -58
  231. package/src/templates/next-pagebuilder/components/page-builder/components/content-collection/skeleton.tsx +4 -16
  232. package/src/templates/next-pagebuilder/components/page-builder/components/content-collection/types.ts +14 -1
  233. package/src/templates/next-pagebuilder/components/page-builder/components/rich-text-block.tsx +16 -0
  234. package/src/templates/next-pagebuilder/components/page-builder/renderer.tsx +21 -7
  235. package/src/templates/next-pagebuilder/components/page-document/index.tsx +1 -0
  236. package/src/templates/next-pagebuilder/components/ui/image/index.tsx +2 -1
  237. package/src/templates/next-pagebuilder/knip.json +3 -2
  238. package/src/templates/next-pagebuilder/lefthook.yml +1 -1
  239. package/src/templates/next-pagebuilder/lib/integrations/sanity/sanity.types.ts +1221 -977
  240. package/src/templates/next-pagebuilder/lib/utils/image-sizes.ts +72 -0
  241. package/src/templates/next-pagebuilder/lib/utils/merge-refs.ts +56 -0
  242. package/src/templates/next-pagebuilder/next.config.ts +2 -3
  243. package/src/templates/next-pagebuilder/package.json +4 -6
  244. package/src/templates/next-pagebuilder-cache/.biome/plugins/README.md +21 -0
  245. package/src/templates/next-pagebuilder-cache/.biome/plugins/no-anchor-element.grit +12 -0
  246. package/src/templates/next-pagebuilder-cache/.biome/plugins/no-relative-parent-imports.grit +10 -0
  247. package/src/templates/next-pagebuilder-cache/.biome/plugins/no-unnecessary-forwardref.grit +9 -0
  248. package/src/templates/next-pagebuilder-cache/.env.example +40 -0
  249. package/src/templates/next-pagebuilder-cache/.github/PULL_REQUEST_TEMPLATE.md +13 -0
  250. package/src/templates/next-pagebuilder-cache/.github/workflows/ci.yml +105 -0
  251. package/src/templates/next-pagebuilder-cache/.vscode/extensions.json +19 -0
  252. package/src/templates/next-pagebuilder-cache/.vscode/settings.json +103 -0
  253. package/src/templates/next-pagebuilder-cache/.zed/settings.json +47 -0
  254. package/src/templates/next-pagebuilder-cache/README.md +212 -0
  255. package/src/templates/next-pagebuilder-cache/_gitignore +65 -0
  256. package/src/templates/next-pagebuilder-cache/app/(content)/[[...slug]]/page.tsx +124 -0
  257. package/src/templates/next-pagebuilder-cache/app/(content)/layout.tsx +28 -0
  258. package/src/templates/next-pagebuilder-cache/app/actions/unlock-gated-content.ts +53 -0
  259. package/src/templates/next-pagebuilder-cache/app/favicon.ico +0 -0
  260. package/src/templates/next-pagebuilder-cache/app/layout.tsx +83 -0
  261. package/src/templates/next-pagebuilder-cache/app/robots.ts +13 -0
  262. package/src/templates/next-pagebuilder-cache/app/sitemap.xml/route.ts +84 -0
  263. package/src/templates/next-pagebuilder-cache/biome.json +257 -0
  264. package/src/templates/next-pagebuilder-cache/components/layout/footer/index.tsx +127 -0
  265. package/src/templates/next-pagebuilder-cache/components/layout/header/components/cta-button.tsx +28 -0
  266. package/src/templates/next-pagebuilder-cache/components/layout/header/components/mega-menu-panel.tsx +90 -0
  267. package/src/templates/next-pagebuilder-cache/components/layout/header/components/nav-item-renderer.tsx +98 -0
  268. package/src/templates/next-pagebuilder-cache/components/layout/header/components/nav-leaf-item.tsx +33 -0
  269. package/src/templates/next-pagebuilder-cache/components/layout/header/components/types.ts +7 -0
  270. package/src/templates/next-pagebuilder-cache/components/layout/header/header-client.tsx +110 -0
  271. package/src/templates/next-pagebuilder-cache/components/layout/header/index.tsx +32 -0
  272. package/src/templates/next-pagebuilder-cache/components/layout/json-ld/index.tsx +79 -0
  273. package/src/templates/next-pagebuilder-cache/components/layout/wrapper/index.tsx +33 -0
  274. package/src/templates/next-pagebuilder-cache/components/page-builder/components/article-content/index.tsx +78 -0
  275. package/src/templates/next-pagebuilder-cache/components/page-builder/components/article-content/related-post-item.tsx +27 -0
  276. package/src/templates/next-pagebuilder-cache/components/page-builder/components/content-collection/content-card.tsx +66 -0
  277. package/src/templates/next-pagebuilder-cache/components/page-builder/components/content-collection/content-filters.tsx +76 -0
  278. package/src/templates/next-pagebuilder-cache/components/page-builder/components/content-collection/content-grid.tsx +40 -0
  279. package/src/templates/next-pagebuilder-cache/components/page-builder/components/content-collection/content-pagination-nav.tsx +71 -0
  280. package/src/templates/next-pagebuilder-cache/components/page-builder/components/content-collection/index.tsx +306 -0
  281. package/src/templates/next-pagebuilder-cache/components/page-builder/components/content-collection/skeleton.tsx +41 -0
  282. package/src/templates/next-pagebuilder-cache/components/page-builder/components/content-collection/types.ts +32 -0
  283. package/src/templates/next-pagebuilder-cache/components/page-builder/components/description.tsx +17 -0
  284. package/src/templates/next-pagebuilder-cache/components/page-builder/components/gated-content/index.tsx +169 -0
  285. package/src/templates/next-pagebuilder-cache/components/page-builder/components/hero.tsx +17 -0
  286. package/src/templates/next-pagebuilder-cache/components/page-builder/components/rich-text-block.tsx +16 -0
  287. package/src/templates/next-pagebuilder-cache/components/page-builder/renderer.tsx +71 -0
  288. package/src/templates/next-pagebuilder-cache/components/page-builder/types.ts +25 -0
  289. package/src/templates/next-pagebuilder-cache/components/page-document/index.tsx +147 -0
  290. package/src/templates/next-pagebuilder-cache/components/ui/image/index.tsx +216 -0
  291. package/src/templates/next-pagebuilder-cache/components/ui/link/index.tsx +152 -0
  292. package/src/templates/next-pagebuilder-cache/components.json +24 -0
  293. package/src/templates/next-pagebuilder-cache/knip.json +35 -0
  294. package/src/templates/next-pagebuilder-cache/lefthook.yml +25 -0
  295. package/src/templates/next-pagebuilder-cache/lib/integrations/sanity/sanity.types.ts +1588 -0
  296. package/src/templates/next-pagebuilder-cache/lib/scripts/setup-ci-secrets.ts +117 -0
  297. package/src/templates/next-pagebuilder-cache/lib/styles/cn.ts +5 -0
  298. package/src/templates/next-pagebuilder-cache/lib/styles/global.css +69 -0
  299. package/src/templates/next-pagebuilder-cache/lib/utils/base-url.ts +17 -0
  300. package/src/templates/next-pagebuilder-cache/lib/utils/format-date.ts +8 -0
  301. package/src/templates/next-pagebuilder-cache/lib/utils/slug-tag.ts +6 -0
  302. package/src/templates/next-pagebuilder-cache/next.config.ts +176 -0
  303. package/src/templates/next-pagebuilder-cache/package.json +62 -0
  304. package/src/templates/next-pagebuilder-cache/postcss.config.mjs +65 -0
  305. package/src/templates/next-pagebuilder-cache/svg.d.ts +6 -0
  306. package/src/templates/next-pagebuilder-cache/tsconfig.json +38 -0
  307. package/src/templates/next-webgl/.env.example +2 -2
  308. package/src/templates/next-webgl/.github/workflows/ci.yml +1 -1
  309. package/src/templates/next-webgl/.vscode/settings.json +0 -2
  310. package/src/templates/next-webgl/components/ui/image/index.tsx +2 -4
  311. package/src/templates/next-webgl/knip.json +1 -1
  312. package/src/templates/next-webgl/lefthook.yml +1 -1
  313. package/src/templates/next-webgl/lib/utils/README.md +18 -5
  314. package/src/templates/next-webgl/lib/utils/image-sizes.ts +72 -0
  315. package/src/templates/next-webgl/lib/utils/merge-refs.ts +56 -0
  316. package/src/templates/next-webgl/lib/utils/strings.ts +1 -51
  317. package/src/templates/next-webgl/next.config.ts +2 -3
  318. package/src/templates/next-webgl/package.json +4 -7
  319. package/src/templates/next-experiments/tsconfig.tsbuildinfo +0 -1
  320. package/src/templates/next-pagebuilder/app/actions/refresh.ts +0 -5
  321. package/src/templates/next-webgl/tsconfig.tsbuildinfo +0 -1
@@ -0,0 +1,212 @@
1
+ # Page Builder Template (Cache Components)
2
+
3
+ Next.js + Sanity CMS page builder architecture. All URL structure is managed in Sanity, not in code.
4
+
5
+ This is the [Cache Components](https://nextjs.org/docs/app/api-reference/config/next-config-js/cacheComponents)
6
+ variant: `cacheComponents: true`, so every Sanity read sits behind a `'use cache'`
7
+ boundary and lands in the static shell, and `cacheLife` runs on a ~1-year window.
8
+ Nothing is time-based — the publish webhook is what invalidates, and
9
+ `<SanityLive />` only runs in draft mode. See "Content invalidation" below; without
10
+ that webhook, published changes never reach the deployed site.
11
+
12
+ ## Getting Started
13
+
14
+ ```bash
15
+ bun dev
16
+ ```
17
+
18
+ ## Architecture
19
+
20
+ - Single catch-all route `[[...slug]]` handles all pages
21
+ - Sanity page builder with composable content blocks
22
+ - Blog with categories, filtering, and pagination
23
+ - Visual editing and live preview support
24
+
25
+ ## Commands
26
+
27
+ - `bun dev` - Start dev server
28
+ - `bun build` - Production build
29
+ - `bun lint` - Run linter
30
+ - `bun sanity:typegen` - Generate Sanity types
31
+ <!-- bsmnt:if dotenvx -->
32
+ - `bun env:set KEY value` - Encrypt one value into `.env` (`--plain`, `--preview`/`--prod`)
33
+ - `bun env:encrypt-all` - Bulk-encrypt every plaintext value in a file (`--preview`/`--prod`)
34
+ - `bun env:setup` - Restore `.env.keys` from 1Password
35
+ - `bun env:view` - Print the decrypted `.env` (`--preview` / `--prod`)
36
+ - `bun env:setup-remote` - Push private keys to GitHub / Vercel (`github`/`vercel`, or pick)
37
+ - `bun setup-remote` - Create the GitHub repo, link a Vercel project, and push the key to Vercel env
38
+ - `bun env:save` - Save the private key to 1Password (Development vault)
39
+ <!-- bsmnt:endif -->
40
+
41
+ <!-- bsmnt:if dotenvx -->
42
+ ## Environment variables
43
+
44
+ Secrets are encrypted at rest with [dotenvx](https://dotenvx.com): the committed `.env` holds encrypted values, the private keys live in `.env.keys` (gitignored), and dev/build run through `dotenvx run` so values decrypt automatically.
45
+
46
+ **First run.** `bun install` restores `.env.keys` from 1Password (via `DOTENV_PRIVATE_KEY_OP_REF` in `.env`), or run `bun run env:setup`. Needs 1Password access + the `op` CLI:
47
+
48
+ - macOS: `brew install --cask 1password-cli`
49
+ - Windows: `winget install 1password-cli`
50
+ - Linux / other: [1Password CLI docs](https://developer.1password.com/docs/cli/get-started/)
51
+
52
+ **Add or change a variable** (in `.env`):
53
+
54
+ - Secret: `bun run env:set KEY value` (`--preview`/`--prod` to target an environment)
55
+ - Public / `NEXT_PUBLIC_*`: `bun run env:set KEY value --plain`
56
+ - Bulk-encrypt a hand-edited file: `bun run env:encrypt-all`
57
+ - Inspect: `bun run env:view` (`--preview`/`--prod`)
58
+
59
+ **Per environment.** Scripts load `.env.$VERCEL_ENV` over `.env`. After adding a per-env file, push its key with `bun run env:setup-remote vercel` — `instrumentation.ts` uses it to decrypt at runtime.
60
+
61
+ **Edge runtime.** dotenvx decrypts only in the Node runtime; secrets read in `middleware.ts` or a `runtime = "edge"` route must be set as plain Vercel env vars.
62
+
63
+ A gitleaks pre-commit hook + CI scan block committed plaintext secrets (install: `brew install gitleaks`).
64
+
65
+ <!-- bsmnt:endif -->
66
+ <!-- bsmnt:if !dotenvx -->
67
+ ## Environment variables
68
+
69
+ Copy `.env.example` to `.env.local` and fill in the values you need. `.env.local` is gitignored, so it stays off version control. Only `NEXT_PUBLIC_*` variables are exposed to the browser — everything else is server-only.
70
+
71
+ For deploys, set the same variables in your host's dashboard (e.g. Vercel → Settings → Environment Variables).
72
+
73
+ <!-- bsmnt:endif -->
74
+ ## Sanity Dashboard (Studio in an iframe)
75
+
76
+ The Studio at `/studio` can be embedded in the Sanity Dashboard
77
+ (`https://www.sanity.io/@<org>/studio/<appId>/<workspace>`), which loads it in an
78
+ iframe from `www.sanity.io`. Three things have to be true for that to work:
79
+
80
+ 1. **Headers.** `next.config.ts` already exempts `/studio/:path*` from the
81
+ site-wide `X-Frame-Options` / `frame-ancestors 'self'` headers, sending
82
+ `frame-ancestors 'self' https://*.sanity.io` there instead and dropping
83
+ `X-Frame-Options` entirely for that path (it has no "allow this origin"
84
+ form — it must be absent, not overridden). Nothing to do here unless
85
+ you've hand-edited `headers()`.
86
+ 2. **Bridge script.** Already wired in: `next-sanity/studio`'s `NextStudio`
87
+ re-exports `NextStudioWithBridge`, which renders the
88
+ `core.sanity-cdn.com/bridge.js` tag the Dashboard needs. Don't hand-roll
89
+ that script tag, and don't switch the Studio route to
90
+ `next-sanity/studio/client-component` (the bridge-less variant).
91
+ 3. **Registered URL.** In [Sanity Manage](https://sanity.io/manage), the
92
+ Studio application's URL must be the **full path**
93
+ (`https://example.com/studio`), not just the domain. This can't be set
94
+ from code — either click through Manage, or run
95
+ `sanity deploy --external --url https://example.com/studio --yes` from
96
+ `lib/integrations/sanity`. Writing it via the API needs the
97
+ `sanity.project.deployStudio` grant, which read/write tokens don't have.
98
+
99
+ Verify against a production build (`bun build && bun start`), not dev. One trap:
100
+ the Dashboard reports `Failed to load iframe due to X-Frame-Options: SAMEORIGIN`
101
+ for *any* framing failure, including a wrong registered URL with already-correct
102
+ headers. Trust the browser console's `Framing '<url>' violates …` line instead —
103
+ it names the URL actually being framed.
104
+
105
+ ## Content invalidation (publish webhook) — required
106
+
107
+ Cached content is invalidated **on demand**, not on a timer: `cacheLife` is set to
108
+ a ~1-year window because a publish is supposed to clear the affected entries. That
109
+ clearing happens in `app/api/revalidate/route.ts`, which only runs if a Sanity
110
+ webhook calls it. **Until you create that webhook, published changes will not
111
+ appear on the deployed site until the next deploy.**
112
+
113
+ `SANITY_REVALIDATE_SECRET` is generated for you during `bsmnt create` (see your
114
+ env file). The webhook itself is manual, because the deployed URL isn't known at
115
+ scaffold time. Once deployed, in **sanity.io/manage → API → Webhooks → Create**:
116
+
117
+ | Field | Value |
118
+ | --- | --- |
119
+ | URL | `https://<your-domain>/api/revalidate` |
120
+ | Dataset | `production` |
121
+ | Trigger on | Create, Update, Delete |
122
+ | Filter | leave empty (the route decides what to invalidate) |
123
+ | Projection | see below — the route needs `operation` and `before()` |
124
+ | HTTP method | `POST` |
125
+ | API version | `v2021-03-25` or later |
126
+ | Secret | the `SANITY_REVALIDATE_SECRET` value from your env |
127
+
128
+ The projection is not optional: the route reads `operation` and the pre-delete
129
+ document to invalidate a removed page, and neither is in the default payload.
130
+
131
+ ```groq
132
+ {
133
+ "_id": _id,
134
+ "_type": _type,
135
+ "operation": delta::operation(),
136
+ "before": before(),
137
+ "after": after()
138
+ }
139
+ ```
140
+
141
+ Make sure the same secret is set in your hosting provider's environment, not just
142
+ locally. To check it end to end: publish a change in the Studio, then reload the
143
+ affected page — it should update without a redeploy. Close any other tab on the
144
+ site first: in draft mode `<SanityLive>` refreshes the router on its own, which
145
+ can make a broken webhook look like it works.
146
+
147
+ ### Watch that it keeps working
148
+
149
+ This webhook is the only thing that invalidates published content — `<SanityLive>`
150
+ is mounted in draft mode only, so nothing else clears a cache entry. A webhook that
151
+ silently stops delivering therefore freezes the site until the next deploy, with no
152
+ other symptom.
153
+
154
+ **sanity.io/manage → API → Webhooks → your webhook → Delivery log** is where to
155
+ check. A 401 means the secret doesn't match; repeated 5xx means the route is
156
+ erroring; no attempts at all means the filter or dataset is wrong. Worth an alert
157
+ if you have somewhere to send one.
158
+
159
+ ### Recovering from a missed invalidation
160
+
161
+ If a page is stale and you don't know which tag was missed, `POST` a purge to the
162
+ same route. It clears every fixed tag plus every published page's per-slug tag —
163
+ including the `/api/<slug>.md` routes, which no `revalidatePath` reaches:
164
+
165
+ ```bash
166
+ curl -X POST https://<your-domain>/api/revalidate \
167
+ -H "content-type: application/json" \
168
+ -H "sanity-webhook-signature: <signature>" \
169
+ -d '{"purgeAll": true}'
170
+ ```
171
+
172
+ The route validates the same signature as a real webhook, so the simplest way to
173
+ send it is a second Sanity webhook with `{"purgeAll": true}` as a fixed projection,
174
+ triggered by hand from sanity.io/manage.
175
+
176
+ ## Continuous Integration
177
+
178
+ A GitHub Actions workflow (`.github/workflows/ci.yml`) runs on every pull request to `main` and on pushes to `main`. It runs type checking, linting, the production build, tests, and `knip`.
179
+
180
+ The workflow takes its runtime versions from your manifests, so there's a single source of truth and nothing to keep in sync by hand:
181
+
182
+ - **Node** comes from `.nvmrc`.
183
+ - **Bun** comes from the `packageManager` field in `package.json`.
184
+
185
+ It also caches Bun's install cache between runs, and the test step is a no-op until you add real test files.
186
+
187
+ <!-- bsmnt:if dotenvx -->
188
+ The build decrypts the committed `.env` with the dotenvx private key, pulled from 1Password: set an **org-level** `OP_SERVICE_ACCOUNT_TOKEN` (a read-only [service account](https://developer.1password.com/docs/service-accounts/) scoped to the `Development` vault) — one secret for every repo — and CI reads the key via the op-ref committed in `.env`. A per-repo `DOTENV_PRIVATE_KEY` secret (`bun run env:setup-remote github`) works as a fallback; builds pass without either (only plaintext / `NEXT_PUBLIC_*` is read).
189
+
190
+ <!-- bsmnt:endif -->
191
+ ### Sanity environment
192
+
193
+ The build reads Sanity config from GitHub Actions variables/secrets (empty and harmless until you configure Sanity). Once your repo is on GitHub, the quickest way to set them is:
194
+
195
+ ```bash
196
+ bun run ci:secrets
197
+ ```
198
+
199
+ This reads the values from `.env.local` and pushes them to this repo with the GitHub CLI — `NEXT_PUBLIC_SANITY_PROJECT_ID` / `NEXT_PUBLIC_SANITY_DATASET` as **Variables** (they are public) and `SANITY_API_READ_TOKEN` as a **Secret**. Requires the [GitHub CLI](https://cli.github.com), authenticated (`gh auth login`).
200
+
201
+ To set them by hand instead, go to **Settings → Secrets and variables → Actions** and add the two `NEXT_PUBLIC_*` values under **Variables** and the token under **Secrets**.
202
+
203
+ ### Branch protection
204
+
205
+ To block merges when CI fails, enable branch protection on `main`:
206
+
207
+ 1. Go to **Settings → Branches → Add branch ruleset** (or **Add rule**).
208
+ 2. Target the `main` branch.
209
+ 3. Enable **Require status checks to pass before merging** and select the **CI** check.
210
+ 4. (Recommended) Enable **Require a pull request before merging**.
211
+
212
+ Pull requests then cannot be merged until the CI workflow passes.
@@ -0,0 +1,65 @@
1
+ # See https://help.github.com/articles/ignoring-files/ for more about ignoring files.
2
+
3
+ # dependencies
4
+ /node_modules
5
+ /.pnp
6
+ .pnp.*
7
+ .turbo
8
+ .npmrc
9
+ .yarn/*
10
+ !.yarn/patches
11
+ !.yarn/plugins
12
+ !.yarn/releases
13
+ !.yarn/versions
14
+
15
+ # testing
16
+ /coverage
17
+
18
+ # next.js
19
+ /.next/
20
+ /out/
21
+
22
+ # TypeScript
23
+ *.tsbuildinfo
24
+
25
+ # production
26
+ /build
27
+ /public/sw.js
28
+
29
+ # misc
30
+ .DS_Store
31
+ *.pem
32
+
33
+ # debug
34
+ npm-debug.log*
35
+ yarn-debug.log*
36
+ yarn-error.log*
37
+
38
+ # local env files
39
+ .env
40
+ .env.local
41
+ .env*.local
42
+ .env.development.local
43
+ .env.test.local
44
+ .env.production.local
45
+
46
+ # vercel
47
+ .vercel
48
+
49
+ # tldr
50
+ .tldr
51
+
52
+ # eslint
53
+ .eslintcache
54
+
55
+ # certificates
56
+ certificates
57
+
58
+ next-env.d.ts
59
+
60
+
61
+ # unlighthouse
62
+ .unlighthouse/
63
+
64
+ # storybook
65
+ *storybook.log
@@ -0,0 +1,124 @@
1
+ import { cacheLife, cacheTag } from "next/cache"
2
+ import { notFound } from "next/navigation"
3
+ import { PageDocument } from "@/components/page-document"
4
+ import {
5
+ type DynamicFetchOptions,
6
+ getDynamicFetchOptions,
7
+ toSanityFetchOptions,
8
+ sanityFetch,
9
+ sanityFetchStaticParams,
10
+ } from "@/lib/integrations/sanity/live"
11
+ import {
12
+ ALL_PAGE_SLUGS_QUERY,
13
+ PAGE_QUERY,
14
+ } from "@/lib/integrations/sanity/queries"
15
+ import { BLOG_CATEGORIES_TAG } from "@/lib/integrations/sanity/tags"
16
+ import type {
17
+ ALL_PAGE_SLUGS_QUERY_RESULT,
18
+ PAGE_QUERY_RESULT,
19
+ } from "@/lib/integrations/sanity/sanity.types"
20
+ import { checkRedirect } from "@/lib/utils/check-redirect"
21
+ import { generateSanityMetadata } from "@/lib/utils/metadata"
22
+ import { getSlugTag } from "@/lib/utils/slug-tag"
23
+
24
+ type SearchParams = Promise<{ [key: string]: string | string[] | undefined }>
25
+ type Props = { params: Promise<{ slug?: string[] }> }
26
+
27
+ const isValidSegment = (segment: string) =>
28
+ segment.length > 0 && segment !== "." && segment !== ".."
29
+
30
+ const toPath = (segments?: string[]) => {
31
+ if (!segments?.length) return null
32
+ if (!segments.every(isValidSegment)) return null
33
+ return segments.join("/")
34
+ }
35
+
36
+ // One cached read of PAGE_QUERY for both the body and generateMetadata — as two
37
+ // functions it was two entries and two fetches per slug. The key must keep every
38
+ // DynamicFetchOptions field: dropping `variant` breaks release previews.
39
+ async function getCachedPage(
40
+ slug: string | null,
41
+ fetchOptions: DynamicFetchOptions
42
+ ): Promise<NonNullable<PAGE_QUERY_RESULT> | null> {
43
+ "use cache"
44
+ const { data } = await sanityFetch({
45
+ query: PAGE_QUERY,
46
+ params: { slug },
47
+ ...toSanityFetchOptions(fetchOptions),
48
+ })
49
+ const page = (data as NonNullable<PAGE_QUERY_RESULT> | null) ?? null
50
+
51
+ if (page) {
52
+ // Tagged inside the boundary so only real pages mint one: the slug is
53
+ // URL-derived, and a per-slug tag can't be enumerated or purged.
54
+ cacheTag(getSlugTag(slug))
55
+
56
+ // The pinned category is dereferenced into this entry, so it expires with it.
57
+ if (page.collectionCategory) cacheTag(BLOG_CATEGORIES_TAG)
58
+ } else {
59
+ // No document, so no publish event will ever clear this entry.
60
+ cacheLife("hours")
61
+ }
62
+
63
+ return page
64
+ }
65
+
66
+ const HOMEPAGE_PARAM: { slug: string[] } = { slug: [] }
67
+
68
+ export const generateStaticParams = async () => {
69
+ const { data: slugs } =
70
+ await sanityFetchStaticParams<ALL_PAGE_SLUGS_QUERY_RESULT>({
71
+ query: ALL_PAGE_SLUGS_QUERY,
72
+ })
73
+
74
+ return [
75
+ HOMEPAGE_PARAM,
76
+ ...(slugs ?? []).flatMap((page) => {
77
+ if (!page.slug || page.slug === "/") return []
78
+ return { slug: page.slug.split("/") }
79
+ }),
80
+ ]
81
+ }
82
+
83
+ export const generateMetadata = async ({ params }: Props) => {
84
+ const [{ slug }, fetchOptions] = await Promise.all([
85
+ params,
86
+ getDynamicFetchOptions(),
87
+ ])
88
+ const path = toPath(slug)
89
+
90
+ if (slug?.length && path === null) return {}
91
+
92
+ // stega off: it would corrupt a <title> or og:image URL, and metadata renders no
93
+ // DOM for <VisualEditing> anyway. In published mode this is the body's key.
94
+ const page = await getCachedPage(path, { ...fetchOptions, stega: false })
95
+
96
+ return page
97
+ ? generateSanityMetadata({ document: page, url: path ? `/${path}` : "/" })
98
+ : {}
99
+ }
100
+
101
+ // No Suspense wrapper needed: `getDynamicFetchOptions` is prerender-safe and
102
+ // `searchParams` is passed down unawaited, so the page prerenders.
103
+ export default async function CatchAllPage({
104
+ params,
105
+ searchParams,
106
+ }: Props & { searchParams: SearchParams }) {
107
+ const [{ slug }, fetchOptions] = await Promise.all([
108
+ params,
109
+ getDynamicFetchOptions(),
110
+ ])
111
+ const path = toPath(slug)
112
+
113
+ if (slug?.length && path === null) notFound()
114
+
115
+ const page = await getCachedPage(path, fetchOptions)
116
+
117
+ if (!page) {
118
+ // Fall back to a CMS redirect before 404ing.
119
+ await checkRedirect(path)
120
+ notFound()
121
+ }
122
+
123
+ return <PageDocument page={page} path={path} searchParams={searchParams} />
124
+ }
@@ -0,0 +1,28 @@
1
+ import { draftMode } from "next/headers"
2
+ import { JsonLd } from "@/components/layout/json-ld"
3
+ import { Wrapper } from "@/components/layout/wrapper"
4
+ import { SanityVisualEditing } from "@/components/sanity/visual-editing"
5
+ import { SanityLive } from "@/lib/integrations/sanity/live"
6
+
7
+ // The one dynamic API allowed at the top of a layout: it still prerenders, and
8
+ // resolving it once lets each chrome component cache its published render.
9
+ const Layout = async ({ children }: { children: React.ReactNode }) => {
10
+ const { isEnabled: isDraftMode } = await draftMode()
11
+
12
+ return (
13
+ <>
14
+ <JsonLd isDraftMode={isDraftMode} />
15
+ <Wrapper isDraftMode={isDraftMode}>{children}</Wrapper>
16
+ {/* Draft mode only: for the public this costs a live connection and a
17
+ server action per tab, and the webhook is what invalidates. */}
18
+ {isDraftMode ? (
19
+ <>
20
+ <SanityLive includeDrafts />
21
+ <SanityVisualEditing />
22
+ </>
23
+ ) : null}
24
+ </>
25
+ )
26
+ }
27
+
28
+ export default Layout
@@ -0,0 +1,53 @@
1
+ "use server"
2
+
3
+ import { client } from "@/lib/integrations/sanity/client"
4
+ import {
5
+ getDynamicFetchOptions,
6
+ toSanityFetchOptions,
7
+ } from "@/lib/integrations/sanity/live"
8
+ import { GATED_CONTENT_QUERY } from "@/lib/integrations/sanity/queries"
9
+ import type { GATED_CONTENT_QUERY_RESULT } from "@/lib/integrations/sanity/sanity.types"
10
+
11
+ export type GatedContentPayload = NonNullable<GATED_CONTENT_QUERY_RESULT>
12
+
13
+ export type UnlockResult =
14
+ | { ok: true; payload: GatedContentPayload }
15
+ | { ok: false; error: string }
16
+
17
+ type UnlockInput = { documentId: string; email: string }
18
+
19
+ export async function unlockGatedContent({
20
+ documentId,
21
+ email,
22
+ }: UnlockInput): Promise<UnlockResult> {
23
+ if (!documentId) return { ok: false, error: "Missing content id." }
24
+ if (!/^[^\s@]+@[^\s@]+\.[^\s@]+$/.test(email)) {
25
+ return { ok: false, error: "Enter a valid email address." }
26
+ }
27
+
28
+ // Lead-capture seam: forward `email` to your CRM / email provider here.
29
+ // This is not access control — the payload is withheld from the page until
30
+ // this action runs, but anyone who submits the form receives it.
31
+
32
+ if (!client) return { ok: false, error: "Content not found." }
33
+
34
+ const fetchOptions = await getDynamicFetchOptions()
35
+
36
+ // `client`, not `directClient`: a read by id needs no post-mutation freshness,
37
+ // so it belongs on the CDN meter. Options threaded whole — hand-picking fields
38
+ // is how `variant` goes missing.
39
+ const payload = await client.fetch<GATED_CONTENT_QUERY_RESULT>(
40
+ GATED_CONTENT_QUERY,
41
+ { id: documentId },
42
+ toSanityFetchOptions({
43
+ ...fetchOptions,
44
+ // stega must stay off: it injects invisible characters into strings, which
45
+ // would corrupt the asset URLs returned for download/playback.
46
+ stega: false,
47
+ }),
48
+ )
49
+
50
+ if (!payload) return { ok: false, error: "Content not found." }
51
+
52
+ return { ok: true, payload }
53
+ }
@@ -0,0 +1,83 @@
1
+ import "@/lib/styles/global.css"
2
+
3
+ import type { Metadata } from "next"
4
+ import { Geist } from "next/font/google"
5
+ import type { PropsWithChildren } from "react"
6
+ import { cn } from "@/lib/styles/cn"
7
+ import AppData from "@/package.json"
8
+
9
+ const APP_NAME = AppData.name
10
+ const APP_DEFAULT_TITLE = "My Site"
11
+ const APP_TITLE_TEMPLATE = "%s"
12
+ const APP_DESCRIPTION = AppData.description
13
+ // Build-time-stable so static routes (Studio, /_not-found) stay prerenderable
14
+ // under Cache Components. NEXT_PUBLIC_* is inlined at build; reading runtime env
15
+ // (VERCEL_URL) here would taint every route's metadata as request-time data.
16
+ const APP_BASE_URL =
17
+ process.env.NEXT_PUBLIC_BASE_URL ?? "http://localhost:3000"
18
+
19
+ const geist = Geist({
20
+ subsets: ["latin"],
21
+ variable: "--font-geist-variable",
22
+ })
23
+
24
+ export const metadata: Metadata = {
25
+ alternates: {
26
+ canonical: "/",
27
+ languages: {
28
+ "en-US": "/en-US",
29
+ },
30
+ },
31
+ appleWebApp: {
32
+ capable: true,
33
+ statusBarStyle: "default",
34
+ title: APP_DEFAULT_TITLE,
35
+ },
36
+ applicationName: APP_NAME,
37
+ authors: [{ name: "basement.studio", url: "https://basement.studio" }],
38
+ description: APP_DESCRIPTION,
39
+ formatDetection: { telephone: false },
40
+ metadataBase: new URL(APP_BASE_URL),
41
+ openGraph: {
42
+ description: APP_DESCRIPTION,
43
+ images: [
44
+ {
45
+ alt: APP_DEFAULT_TITLE,
46
+ height: 630,
47
+ url: "/opengraph-image.jpg",
48
+ width: 1200,
49
+ },
50
+ ],
51
+ locale: "en_US",
52
+ siteName: APP_NAME,
53
+ title: {
54
+ default: APP_DEFAULT_TITLE,
55
+ template: APP_TITLE_TEMPLATE,
56
+ },
57
+ type: "website",
58
+ url: APP_BASE_URL,
59
+ },
60
+ other: {
61
+ "fb:app_id": process.env.NEXT_PUBLIC_FACEBOOK_APP_ID || "",
62
+ },
63
+ title: {
64
+ default: APP_DEFAULT_TITLE,
65
+ template: APP_TITLE_TEMPLATE,
66
+ },
67
+ twitter: {
68
+ card: "summary_large_image",
69
+ description: APP_DESCRIPTION,
70
+ title: {
71
+ default: APP_DEFAULT_TITLE,
72
+ template: APP_TITLE_TEMPLATE,
73
+ },
74
+ },
75
+ }
76
+
77
+ const Layout = ({ children }: PropsWithChildren) => (
78
+ <html lang="en" dir="ltr" className={cn(geist.variable)}>
79
+ <body className="text-black">{children}</body>
80
+ </html>
81
+ )
82
+
83
+ export default Layout
@@ -0,0 +1,13 @@
1
+ import type { MetadataRoute } from "next"
2
+ import { getBaseUrl } from "@/lib/utils/base-url"
3
+
4
+ export default function robots(): MetadataRoute.Robots {
5
+ return {
6
+ rules: {
7
+ userAgent: "*",
8
+ allow: "/",
9
+ disallow: [],
10
+ },
11
+ sitemap: `${getBaseUrl()}/sitemap.xml`,
12
+ }
13
+ }
@@ -0,0 +1,84 @@
1
+ import { NextResponse } from "next/server"
2
+ import { isSanityConfigured } from "@/lib/integrations/check-integration"
3
+ import type { PAGE_SITEMAP_QUERY_RESULT } from "@/lib/integrations/sanity/sanity.types"
4
+ import { getPageUrl, getSitemap, getSitemapBaseUrl } from "@/lib/utils/sitemap"
5
+
6
+ type PageDoc = PAGE_SITEMAP_QUERY_RESULT[number]
7
+
8
+ interface SitemapEntry {
9
+ url: string
10
+ lastModified?: string | null
11
+ }
12
+
13
+ const createPageEntry = (
14
+ baseUrl: string,
15
+ slug: string | null,
16
+ updatedAt?: string | null
17
+ ): SitemapEntry => ({
18
+ url: getPageUrl(baseUrl, slug),
19
+ lastModified: updatedAt ?? null,
20
+ })
21
+
22
+ const escapeXml = (value: string) =>
23
+ value
24
+ .replaceAll("&", "&amp;")
25
+ .replaceAll("<", "&lt;")
26
+ .replaceAll(">", "&gt;")
27
+ .replaceAll('"', "&quot;")
28
+ .replaceAll("'", "&apos;")
29
+
30
+ const renderUrl = (entry: SitemapEntry) => {
31
+ const parts = [" <url>", ` <loc>${escapeXml(entry.url)}</loc>`]
32
+
33
+ if (entry.lastModified) {
34
+ parts.push(
35
+ ` <lastmod>${new Date(entry.lastModified).toISOString()}</lastmod>`
36
+ )
37
+ }
38
+
39
+ parts.push(" </url>")
40
+
41
+ return parts.join("\n")
42
+ }
43
+
44
+ const renderSitemap = (entries: SitemapEntry[]) =>
45
+ [
46
+ '<?xml version="1.0" encoding="UTF-8"?>',
47
+ '<urlset xmlns="http://www.sitemaps.org/schemas/sitemap/0.9">',
48
+ ...entries.map(renderUrl),
49
+ "</urlset>",
50
+ ].join("\n")
51
+
52
+ const getSitemapResponse = (entries: SitemapEntry[], status = 200) =>
53
+ new NextResponse(renderSitemap(entries), {
54
+ status,
55
+ headers: { "Content-Type": "application/xml; charset=utf-8" },
56
+ })
57
+
58
+ // No `connection()`: per-request rendering meant a Sanity round trip per crawler
59
+ // hit. No `Cache-Control` either — a CDN copy is a second cache the webhook
60
+ // can't purge.
61
+ export const GET = async () => {
62
+ const baseUrl = getSitemapBaseUrl()
63
+ const homepageEntry = createPageEntry(baseUrl, null)
64
+
65
+ if (!isSanityConfigured()) return getSitemapResponse([homepageEntry])
66
+
67
+ try {
68
+ const allPages = await getSitemap()
69
+
70
+ if (!allPages) return getSitemapResponse([homepageEntry])
71
+
72
+ const pages = (allPages ?? []).filter((page) => !page.noIndex)
73
+ const sitemapEntries = pages.map((page: PageDoc) =>
74
+ createPageEntry(baseUrl, page.slug ?? null, page._updatedAt)
75
+ )
76
+
77
+ return getSitemapResponse(
78
+ sitemapEntries.length > 0 ? sitemapEntries : [homepageEntry]
79
+ )
80
+ } catch (error) {
81
+ console.error("Error generating sitemap.xml:", error)
82
+ return getSitemapResponse([homepageEntry], 503)
83
+ }
84
+ }