bsmnt 0.4.3 → 0.6.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 (266) hide show
  1. package/README.md +1 -0
  2. package/dist/application/add-hooks/index.d.ts.map +1 -1
  3. package/dist/application/add-hooks/index.js +1 -0
  4. package/dist/application/add-hooks/index.js.map +1 -1
  5. package/dist/application/add-integration/index.d.ts +33 -8
  6. package/dist/application/add-integration/index.d.ts.map +1 -1
  7. package/dist/application/add-integration/index.js +221 -90
  8. package/dist/application/add-integration/index.js.map +1 -1
  9. package/dist/application/create-project/build-context.d.ts +3 -1
  10. package/dist/application/create-project/build-context.d.ts.map +1 -1
  11. package/dist/application/create-project/build-context.js +2 -1
  12. package/dist/application/create-project/build-context.js.map +1 -1
  13. package/dist/application/create-project/index.d.ts +3 -1
  14. package/dist/application/create-project/index.d.ts.map +1 -1
  15. package/dist/application/create-project/index.js +4 -2
  16. package/dist/application/create-project/index.js.map +1 -1
  17. package/dist/application/create-project/map-selection-to-registry.js +1 -1
  18. package/dist/application/create-project/map-selection-to-registry.js.map +1 -1
  19. package/dist/application/install-skills/index.js +1 -1
  20. package/dist/application/install-skills/index.js.map +1 -1
  21. package/dist/application/shared/project-detection.d.ts +2 -2
  22. package/dist/application/shared/project-detection.d.ts.map +1 -1
  23. package/dist/application/shared/project-detection.js +2 -2
  24. package/dist/application/shared/project-detection.js.map +1 -1
  25. package/dist/core/create/execute-plan.d.ts +4 -0
  26. package/dist/core/create/execute-plan.d.ts.map +1 -1
  27. package/dist/core/create/execute-plan.js +8 -0
  28. package/dist/core/create/execute-plan.js.map +1 -1
  29. package/dist/core/create/internal-ids.d.ts +4 -1
  30. package/dist/core/create/internal-ids.d.ts.map +1 -1
  31. package/dist/core/create/internal-ids.js +6 -1
  32. package/dist/core/create/internal-ids.js.map +1 -1
  33. package/dist/core/create/types.d.ts +2 -0
  34. package/dist/core/create/types.d.ts.map +1 -1
  35. package/dist/domain/cms.d.ts +0 -7
  36. package/dist/domain/cms.d.ts.map +1 -1
  37. package/dist/domain/cms.js.map +1 -1
  38. package/dist/domain/forms.d.ts +17 -0
  39. package/dist/domain/forms.d.ts.map +1 -0
  40. package/dist/domain/forms.js +13 -0
  41. package/dist/domain/forms.js.map +1 -0
  42. package/dist/domain/integration.d.ts +30 -0
  43. package/dist/domain/integration.d.ts.map +1 -0
  44. package/dist/domain/integration.js +12 -0
  45. package/dist/domain/integration.js.map +1 -0
  46. package/dist/domain/skills.d.ts +58 -0
  47. package/dist/domain/skills.d.ts.map +1 -1
  48. package/dist/domain/skills.js +52 -0
  49. package/dist/domain/skills.js.map +1 -1
  50. package/dist/index.js +114 -38
  51. package/dist/index.js.map +1 -1
  52. package/dist/infrastructure/agents/register-sanity-mcp.js +9 -6
  53. package/dist/infrastructure/agents/register-sanity-mcp.js.map +1 -1
  54. package/dist/infrastructure/create/apply-package-json.d.ts.map +1 -1
  55. package/dist/infrastructure/create/apply-package-json.js +19 -0
  56. package/dist/infrastructure/create/apply-package-json.js.map +1 -1
  57. package/dist/infrastructure/create/executor.d.ts.map +1 -1
  58. package/dist/infrastructure/create/executor.js +5 -0
  59. package/dist/infrastructure/create/executor.js.map +1 -1
  60. package/dist/infrastructure/create/install-dependencies.d.ts.map +1 -1
  61. package/dist/infrastructure/create/install-dependencies.js +4 -0
  62. package/dist/infrastructure/create/install-dependencies.js.map +1 -1
  63. package/dist/infrastructure/create/lifecycle-registry.d.ts.map +1 -1
  64. package/dist/infrastructure/create/lifecycle-registry.js +2 -0
  65. package/dist/infrastructure/create/lifecycle-registry.js.map +1 -1
  66. package/dist/infrastructure/create/merge-registry.d.ts.map +1 -1
  67. package/dist/infrastructure/create/merge-registry.js +8 -0
  68. package/dist/infrastructure/create/merge-registry.js.map +1 -1
  69. package/dist/infrastructure/create/setup-agent.d.ts +2 -0
  70. package/dist/infrastructure/create/setup-agent.d.ts.map +1 -1
  71. package/dist/infrastructure/create/setup-agent.js +32 -8
  72. package/dist/infrastructure/create/setup-agent.js.map +1 -1
  73. package/dist/infrastructure/create/setup-dotenvx.d.ts +41 -0
  74. package/dist/infrastructure/create/setup-dotenvx.d.ts.map +1 -0
  75. package/dist/infrastructure/create/setup-dotenvx.js +502 -0
  76. package/dist/infrastructure/create/setup-dotenvx.js.map +1 -0
  77. package/dist/infrastructure/create/setup-sanity.d.ts +13 -1
  78. package/dist/infrastructure/create/setup-sanity.d.ts.map +1 -1
  79. package/dist/infrastructure/create/setup-sanity.js +74 -31
  80. package/dist/infrastructure/create/setup-sanity.js.map +1 -1
  81. package/dist/infrastructure/integrations/inject-integration.d.ts +3 -3
  82. package/dist/infrastructure/integrations/inject-integration.d.ts.map +1 -1
  83. package/dist/infrastructure/integrations/inject-integration.js +81 -37
  84. package/dist/infrastructure/integrations/inject-integration.js.map +1 -1
  85. package/dist/modules/features/cms/sanity/config.d.ts.map +1 -1
  86. package/dist/modules/features/cms/sanity/config.js +13 -6
  87. package/dist/modules/features/cms/sanity/config.js.map +1 -1
  88. package/dist/modules/features/cms/sanity/index.js +8 -2
  89. package/dist/modules/features/cms/sanity/index.js.map +1 -1
  90. package/dist/modules/features/cms/sanity/mergers/ci-workflow.d.ts +13 -0
  91. package/dist/modules/features/cms/sanity/mergers/ci-workflow.d.ts.map +1 -0
  92. package/dist/modules/features/cms/sanity/mergers/ci-workflow.js +38 -0
  93. package/dist/modules/features/cms/sanity/mergers/ci-workflow.js.map +1 -0
  94. package/dist/modules/features/cms/sanity/mergers/knip.d.ts +12 -0
  95. package/dist/modules/features/cms/sanity/mergers/knip.d.ts.map +1 -0
  96. package/dist/modules/features/cms/sanity/mergers/knip.js +38 -0
  97. package/dist/modules/features/cms/sanity/mergers/knip.js.map +1 -0
  98. package/dist/modules/features/cms/sanity/mergers/layout.d.ts.map +1 -1
  99. package/dist/modules/features/cms/sanity/mergers/layout.js +17 -3
  100. package/dist/modules/features/cms/sanity/mergers/layout.js.map +1 -1
  101. package/dist/modules/features/cms/sanity/mergers/sitemap.d.ts +3 -3
  102. package/dist/modules/features/cms/sanity/mergers/sitemap.d.ts.map +1 -1
  103. package/dist/modules/features/cms/sanity/mergers/sitemap.js +48 -62
  104. package/dist/modules/features/cms/sanity/mergers/sitemap.js.map +1 -1
  105. package/dist/modules/features/cms/sanity-pagebuilder/config.d.ts.map +1 -1
  106. package/dist/modules/features/cms/sanity-pagebuilder/config.js +8 -5
  107. package/dist/modules/features/cms/sanity-pagebuilder/config.js.map +1 -1
  108. package/dist/modules/features/cms/sanity-pagebuilder/index.js +2 -2
  109. package/dist/modules/features/cms/sanity-pagebuilder/index.js.map +1 -1
  110. package/dist/modules/features/env/dotenvx/config.d.ts +13 -0
  111. package/dist/modules/features/env/dotenvx/config.d.ts.map +1 -0
  112. package/dist/modules/features/env/dotenvx/config.js +47 -0
  113. package/dist/modules/features/env/dotenvx/config.js.map +1 -0
  114. package/dist/modules/features/forms/hubspot/config.d.ts +31 -0
  115. package/dist/modules/features/forms/hubspot/config.d.ts.map +1 -0
  116. package/dist/modules/features/forms/hubspot/config.js +25 -0
  117. package/dist/modules/features/forms/hubspot/config.js.map +1 -0
  118. package/dist/paths.d.ts +16 -0
  119. package/dist/paths.d.ts.map +1 -1
  120. package/dist/paths.js +16 -0
  121. package/dist/paths.js.map +1 -1
  122. package/package.json +21 -9
  123. package/src/agent-skills/json-ld/SKILL.md +85 -0
  124. package/src/agent-skills/manage-env/SKILL.md +67 -0
  125. package/src/assets/check-node-version.mjs +22 -0
  126. package/src/modules/features/cms/sanity/files/app/blog/[slug]/page.tsx +48 -21
  127. package/src/modules/features/cms/sanity/files/app/blog/page.tsx +54 -0
  128. package/src/modules/features/cms/sanity/files/app/feed.xml/route.ts +36 -0
  129. package/src/modules/features/cms/sanity/files/components/sanity/structured-data.tsx +83 -0
  130. package/src/modules/features/cms/sanity/files/lib/integrations/sanity/components/disable-draft-mode.tsx +3 -3
  131. package/src/modules/features/cms/sanity/files/lib/integrations/sanity/queries.ts +14 -0
  132. package/src/modules/features/cms/sanity/files/lib/integrations/sanity/sanity.config.ts +19 -3
  133. package/src/modules/features/cms/sanity/files/lib/integrations/sanity/sanity.types.ts +1 -0
  134. package/src/modules/features/cms/sanity/files/lib/integrations/sanity/schemas/article.ts +7 -7
  135. package/src/modules/features/cms/sanity/files/lib/integrations/sanity/schemas/index.ts +36 -1
  136. package/src/modules/features/cms/sanity/files/lib/integrations/sanity/schemas/metadata.ts +12 -1
  137. package/src/modules/features/cms/sanity/files/lib/integrations/sanity/schemas/structured-data-blocks.ts +416 -0
  138. package/src/modules/features/cms/sanity/files/lib/integrations/sanity/schemas/structured-data.ts +100 -0
  139. package/src/modules/features/cms/sanity/files/lib/integrations/sanity/structure.ts +37 -2
  140. package/src/modules/features/cms/sanity/files/lib/integrations/sanity/utils/page-json-ld.ts +208 -0
  141. package/src/modules/features/cms/sanity/files/lib/scripts/setup-ci-secrets.ts +117 -0
  142. package/src/modules/features/cms/sanity/files/lib/utils/rss.ts +96 -0
  143. package/src/modules/features/cms/sanity-pagebuilder/files/.knip.json +8 -0
  144. package/src/modules/features/cms/sanity-pagebuilder/files/app/api/[[...slug]]/route.ts +100 -0
  145. package/src/modules/features/cms/sanity-pagebuilder/files/app/api/draft-mode/disable/route.ts +7 -0
  146. package/src/modules/features/cms/sanity-pagebuilder/files/app/api/draft-mode/enable/route.ts +20 -0
  147. package/src/modules/features/cms/sanity-pagebuilder/files/app/api/revalidate/route.ts +126 -0
  148. package/src/modules/features/cms/sanity-pagebuilder/files/app/feed.xml/route.ts +40 -0
  149. package/src/modules/features/cms/sanity-pagebuilder/files/app/sitemap.md/route.ts +124 -0
  150. package/src/modules/features/cms/sanity-pagebuilder/files/app/studio/[[...tool]]/page.tsx +8 -0
  151. package/src/modules/features/cms/sanity-pagebuilder/files/components/sanity/draft-mode-toggle.tsx +27 -0
  152. package/src/modules/features/cms/sanity-pagebuilder/files/components/sanity/rich-text.tsx +87 -0
  153. package/src/modules/features/cms/sanity-pagebuilder/files/components/sanity/visual-editing.tsx +28 -0
  154. package/src/modules/features/cms/sanity-pagebuilder/files/components/ui/sanity-image/index.tsx +41 -0
  155. package/src/modules/features/cms/sanity-pagebuilder/files/lib/integrations/check-integration.ts +5 -0
  156. package/src/modules/features/cms/sanity-pagebuilder/files/lib/integrations/sanity/client.ts +27 -0
  157. package/src/modules/features/cms/sanity-pagebuilder/files/lib/integrations/sanity/components/disable-draft-mode.tsx +23 -0
  158. package/src/modules/features/cms/sanity-pagebuilder/files/lib/integrations/sanity/components/page-builder-input.tsx +39 -0
  159. package/src/modules/features/cms/sanity-pagebuilder/files/lib/integrations/sanity/components/page-category-input.tsx +50 -0
  160. package/src/modules/features/cms/sanity-pagebuilder/files/lib/integrations/sanity/components/same-as-input.tsx +66 -0
  161. package/src/modules/features/cms/sanity-pagebuilder/files/lib/integrations/sanity/components/slug-field.tsx +18 -0
  162. package/src/modules/features/cms/sanity-pagebuilder/files/lib/integrations/sanity/components/slug-input.tsx +14 -0
  163. package/src/modules/features/cms/sanity-pagebuilder/files/lib/integrations/sanity/confirm-publish-action.ts +40 -0
  164. package/src/modules/features/cms/sanity-pagebuilder/files/lib/integrations/sanity/env.ts +34 -0
  165. package/src/modules/features/cms/sanity-pagebuilder/files/lib/integrations/sanity/fetchers/layout.ts +67 -0
  166. package/src/modules/features/cms/sanity-pagebuilder/files/lib/integrations/sanity/fetchers/redirects.ts +17 -0
  167. package/src/modules/features/cms/sanity-pagebuilder/files/lib/integrations/sanity/icons.ts +79 -0
  168. package/src/modules/features/cms/sanity-pagebuilder/files/lib/integrations/sanity/live/index.tsx +88 -0
  169. package/src/modules/features/cms/sanity-pagebuilder/files/lib/integrations/sanity/markdown-proxy.config.ts +50 -0
  170. package/src/modules/features/cms/sanity-pagebuilder/files/lib/integrations/sanity/page-builder-config.ts +130 -0
  171. package/src/modules/features/cms/sanity-pagebuilder/files/lib/integrations/sanity/page-category.ts +28 -0
  172. package/src/modules/features/cms/sanity-pagebuilder/files/lib/integrations/sanity/page-tree-pane.tsx +420 -0
  173. package/src/modules/features/cms/sanity-pagebuilder/files/lib/integrations/sanity/pinned-pages.ts +54 -0
  174. package/src/modules/features/cms/sanity-pagebuilder/files/lib/integrations/sanity/presentation.ts +119 -0
  175. package/src/modules/features/cms/sanity-pagebuilder/files/lib/integrations/sanity/queries.ts +407 -0
  176. package/src/modules/features/cms/sanity-pagebuilder/files/lib/integrations/sanity/sanity.cli.ts +29 -0
  177. package/src/modules/features/cms/sanity-pagebuilder/files/lib/integrations/sanity/sanity.config.ts +113 -0
  178. package/src/modules/features/cms/sanity-pagebuilder/files/lib/integrations/sanity/schemas/components/index.ts +4 -0
  179. package/src/modules/features/cms/sanity-pagebuilder/files/lib/integrations/sanity/schemas/components/reusable/blog-content.ts +89 -0
  180. package/src/modules/features/cms/sanity-pagebuilder/files/lib/integrations/sanity/schemas/components/reusable/description.ts +29 -0
  181. package/src/modules/features/cms/sanity-pagebuilder/files/lib/integrations/sanity/schemas/components/reusable/hero.ts +28 -0
  182. package/src/modules/features/cms/sanity-pagebuilder/files/lib/integrations/sanity/schemas/components/singleton/content-collection.ts +45 -0
  183. package/src/modules/features/cms/sanity-pagebuilder/files/lib/integrations/sanity/schemas/content/author.ts +70 -0
  184. package/src/modules/features/cms/sanity-pagebuilder/files/lib/integrations/sanity/schemas/content/blog-category.ts +55 -0
  185. package/src/modules/features/cms/sanity-pagebuilder/files/lib/integrations/sanity/schemas/index.ts +124 -0
  186. package/src/modules/features/cms/sanity-pagebuilder/files/lib/integrations/sanity/schemas/layout/company-data.ts +70 -0
  187. package/src/modules/features/cms/sanity-pagebuilder/files/lib/integrations/sanity/schemas/layout/footer.ts +79 -0
  188. package/src/modules/features/cms/sanity-pagebuilder/files/lib/integrations/sanity/schemas/layout/navbar.ts +75 -0
  189. package/src/modules/features/cms/sanity-pagebuilder/files/lib/integrations/sanity/schemas/layout/redirect.ts +120 -0
  190. package/src/modules/features/cms/sanity-pagebuilder/files/lib/integrations/sanity/schemas/layout/structured-data.ts +124 -0
  191. package/src/modules/features/cms/sanity-pagebuilder/files/lib/integrations/sanity/schemas/shared/link.ts +125 -0
  192. package/src/modules/features/cms/sanity-pagebuilder/files/lib/integrations/sanity/schemas/shared/logo-field.ts +9 -0
  193. package/src/modules/features/cms/sanity-pagebuilder/files/lib/integrations/sanity/schemas/shared/metadata.ts +79 -0
  194. package/src/modules/features/cms/sanity-pagebuilder/files/lib/integrations/sanity/schemas/shared/nav-objects.ts +192 -0
  195. package/src/modules/features/cms/sanity-pagebuilder/files/lib/integrations/sanity/schemas/shared/page-builder.ts +39 -0
  196. package/src/modules/features/cms/sanity-pagebuilder/files/lib/integrations/sanity/schemas/shared/page-folder.ts +124 -0
  197. package/src/modules/features/cms/sanity-pagebuilder/files/lib/integrations/sanity/schemas/shared/page.ts +242 -0
  198. package/src/modules/features/cms/sanity-pagebuilder/files/lib/integrations/sanity/schemas/shared/richText.ts +63 -0
  199. package/src/modules/features/cms/sanity-pagebuilder/files/lib/integrations/sanity/schemas/shared/structured-data-blocks.ts +436 -0
  200. package/src/modules/features/cms/sanity-pagebuilder/files/lib/integrations/sanity/singletons.ts +51 -0
  201. package/src/modules/features/cms/sanity-pagebuilder/files/lib/integrations/sanity/structure.ts +330 -0
  202. package/src/modules/features/cms/sanity-pagebuilder/files/lib/integrations/sanity/utils/image.ts +8 -0
  203. package/src/modules/features/cms/sanity-pagebuilder/files/lib/integrations/sanity/utils/link.ts +140 -0
  204. package/src/modules/features/cms/sanity-pagebuilder/files/lib/integrations/sanity/utils/page-builder-markdown.ts +81 -0
  205. package/src/modules/features/cms/sanity-pagebuilder/files/lib/integrations/sanity/utils/page-json-ld.ts +208 -0
  206. package/src/modules/features/cms/sanity-pagebuilder/files/lib/scripts/sanity-typegen.ts +45 -0
  207. package/src/modules/features/cms/sanity-pagebuilder/files/lib/utils/check-redirect.ts +30 -0
  208. package/src/modules/features/cms/sanity-pagebuilder/files/lib/utils/json-ld.tsx +521 -0
  209. package/src/modules/features/cms/sanity-pagebuilder/files/lib/utils/metadata.ts +167 -0
  210. package/src/modules/features/cms/sanity-pagebuilder/files/lib/utils/rss.ts +96 -0
  211. package/src/modules/features/cms/sanity-pagebuilder/files/lib/utils/sitemap.ts +40 -0
  212. package/src/modules/features/cms/sanity-pagebuilder/files/proxy.ts +81 -0
  213. package/src/modules/features/env/dotenvx/files/decrypt-env.mjs +45 -0
  214. package/src/modules/features/env/dotenvx/files/gitleaks.yml +25 -0
  215. package/src/modules/features/env/dotenvx/files/instrumentation.ts +14 -0
  216. package/src/modules/features/env/dotenvx/files/push-keys.mjs +129 -0
  217. package/src/modules/features/env/dotenvx/files/save-key.mjs +108 -0
  218. package/src/modules/features/env/dotenvx/files/setup-env.mjs +67 -0
  219. package/src/modules/features/env/dotenvx/files/vercelignore +4 -0
  220. package/src/modules/features/env/dotenvx/files/write-env.mjs +47 -0
  221. package/src/templates/next-default/.env.example +14 -6
  222. package/src/templates/next-default/.github/workflows/ci.yml +83 -0
  223. package/src/templates/next-default/.vscode/extensions.json +3 -0
  224. package/src/templates/next-default/README.md +53 -0
  225. package/src/templates/next-default/knip.json +14 -0
  226. package/src/templates/next-default/lefthook.yml +5 -0
  227. package/src/templates/next-default/lib/utils/json-ld.tsx +327 -21
  228. package/src/templates/next-default/next.config.ts +6 -0
  229. package/src/templates/next-default/package.json +8 -4
  230. package/src/templates/next-experiments/.env.example +14 -6
  231. package/src/templates/next-experiments/.github/workflows/ci.yml +83 -0
  232. package/src/templates/next-experiments/.vscode/extensions.json +3 -0
  233. package/src/templates/next-experiments/README.md +53 -0
  234. package/src/templates/next-experiments/knip.json +14 -0
  235. package/src/templates/next-experiments/lefthook.yml +30 -0
  236. package/src/templates/next-experiments/lib/utils/json-ld.tsx +327 -21
  237. package/src/templates/next-experiments/next.config.ts +6 -0
  238. package/src/templates/next-experiments/package.json +10 -2
  239. package/src/templates/next-pagebuilder/.env.example +19 -1
  240. package/src/templates/next-pagebuilder/.github/workflows/ci.yml +90 -0
  241. package/src/templates/next-pagebuilder/.vscode/extensions.json +3 -0
  242. package/src/templates/next-pagebuilder/README.md +65 -0
  243. package/src/templates/next-pagebuilder/app/(content)/[[...slug]]/page.tsx +6 -1
  244. package/src/templates/next-pagebuilder/app/(content)/layout.tsx +3 -12
  245. package/src/templates/next-pagebuilder/components/layout/footer/index.tsx +11 -10
  246. package/src/templates/next-pagebuilder/components/layout/header/index.tsx +5 -3
  247. package/src/templates/next-pagebuilder/components/layout/json-ld/index.tsx +38 -26
  248. package/src/templates/next-pagebuilder/components/layout/wrapper/index.tsx +3 -11
  249. package/src/templates/next-pagebuilder/components/page-builder/components/content-collection/index.tsx +1 -1
  250. package/src/templates/next-pagebuilder/components/page-document/index.tsx +38 -0
  251. package/src/templates/next-pagebuilder/knip.json +27 -0
  252. package/src/templates/next-pagebuilder/lefthook.yml +5 -0
  253. package/src/templates/next-pagebuilder/lib/integrations/sanity/sanity.types.ts +36 -2
  254. package/src/templates/next-pagebuilder/lib/scripts/setup-ci-secrets.ts +117 -0
  255. package/src/templates/next-pagebuilder/next.config.ts +6 -0
  256. package/src/templates/next-pagebuilder/package.json +9 -3
  257. package/src/templates/next-webgl/.env.example +14 -6
  258. package/src/templates/next-webgl/.github/workflows/ci.yml +83 -0
  259. package/src/templates/next-webgl/.vscode/extensions.json +3 -0
  260. package/src/templates/next-webgl/README.md +53 -0
  261. package/src/templates/next-webgl/knip.json +13 -0
  262. package/src/templates/next-webgl/lefthook.yml +5 -0
  263. package/src/templates/next-webgl/lib/utils/json-ld.tsx +327 -21
  264. package/src/templates/next-webgl/next.config.ts +6 -0
  265. package/src/templates/next-webgl/package.json +8 -4
  266. package/src/modules/features/cms/sanity/files/lib/utils/json-ld.tsx +0 -244
@@ -0,0 +1,126 @@
1
+ import { revalidatePath, revalidateTag } from "next/cache";
2
+ import { type NextRequest, NextResponse } from "next/server";
3
+ import { parseBody } from "next-sanity/webhook";
4
+ import { client } from "@/lib/integrations/sanity/client";
5
+ import { SANITY_LAYOUT_TAGS } from "@/lib/integrations/sanity/fetchers/layout";
6
+ import { SANITY_REDIRECTS_TAG } from "@/lib/integrations/sanity/fetchers/redirects";
7
+ import { pageBuilderReferenceMembers as pbrm } from "@/lib/integrations/sanity/page-builder-config";
8
+ import {
9
+ P_REF_PB_COMP_QUERY,
10
+ P_REV_SLUGS_QUERY,
11
+ } from "@/lib/integrations/sanity/queries";
12
+ import type { P_REF_PB_COMP_QUERY_RESULT } from "@/lib/integrations/sanity/sanity.types";
13
+ import { getPagePath } from "@/lib/utils/sitemap";
14
+
15
+ const ERRORS = {
16
+ BAD_REQ: ["Bad Request", { status: 400 }],
17
+ INVALID_SIG: ["Invalid signature", { status: 401 }],
18
+ } as const;
19
+
20
+ type WebhookSlug = {
21
+ current?: string | null;
22
+ };
23
+
24
+ type WebhookDocument = {
25
+ _id: string;
26
+ _type: string;
27
+ resolvedSlug?: string | null;
28
+ slug?: WebhookSlug | null;
29
+ };
30
+
31
+ type MutationOperation = "create" | "update" | "delete";
32
+
33
+ type RevalidateWebhookBody = WebhookDocument & {
34
+ operation?: MutationOperation;
35
+ before?: Partial<WebhookDocument> | null;
36
+ after?: Partial<WebhookDocument> | null;
37
+ };
38
+
39
+ const pageBuilderComponent = new Set<string>(pbrm.map((m) => m.documentType));
40
+
41
+ const normalizeRevalidationSlug = (slug: string | null | undefined) => {
42
+ if (slug === undefined) return null;
43
+ if (slug === null) return "/";
44
+ return `/${slug}`;
45
+ };
46
+
47
+ const getCanonicalPageSlug = async (documentId: string) => {
48
+ if (!client) return null;
49
+
50
+ const pageSlugs = await client.fetch<Array<{ slug: string | null }>>(
51
+ P_REV_SLUGS_QUERY,
52
+ { documentIds: [documentId] },
53
+ );
54
+
55
+ return normalizeRevalidationSlug(
56
+ pageSlugs.find((page) => page.slug !== undefined)?.slug,
57
+ );
58
+ };
59
+
60
+ const getPagesThatReferenceComp = async (documentId: string) => {
61
+ if (!client) return [];
62
+
63
+ const pages = await client.fetch<P_REF_PB_COMP_QUERY_RESULT>(
64
+ P_REF_PB_COMP_QUERY,
65
+ {
66
+ documentId,
67
+ },
68
+ );
69
+
70
+ return (pages ?? []).map((page) => normalizeRevalidationSlug(page.slug));
71
+ };
72
+
73
+ const getAffectedPageSlugsForWebhook = async (body: RevalidateWebhookBody) => {
74
+ const pages: Array<string | null> = [];
75
+
76
+ if (body.operation === "delete" && body?.before?.slug?.current) {
77
+ revalidateTag(body.before.slug.current, "max");
78
+ }
79
+
80
+ // If the document is a page, get its slug
81
+ if (body._type === "page") {
82
+ const data = await getCanonicalPageSlug(body._id);
83
+ if (data !== null) pages.push(data);
84
+ }
85
+
86
+ // If the document is a page builder component, get the page slugs that reference it
87
+ if (pageBuilderComponent.has(body._type)) {
88
+ const data = await getPagesThatReferenceComp(body._id);
89
+ pages.push(...data);
90
+ }
91
+
92
+ return pages.filter((page): page is string => page !== null);
93
+ };
94
+
95
+ export async function POST(request: NextRequest) {
96
+ try {
97
+ const { body, isValidSignature } = await parseBody<RevalidateWebhookBody>(
98
+ request,
99
+ process.env.SANITY_REVALIDATE_SECRET,
100
+ );
101
+
102
+ if (!isValidSignature) return new Response(...ERRORS.INVALID_SIG);
103
+ if (!(body?._type && body._id)) return new Response(...ERRORS.BAD_REQ);
104
+ if (body._id.startsWith("drafts."))
105
+ return NextResponse.json({ status: 200, now: Date.now(), skipped: true });
106
+
107
+ const affectedPageSlugs = await getAffectedPageSlugsForWebhook(body);
108
+
109
+ for (const slug of affectedPageSlugs) {
110
+ revalidatePath(getPagePath(slug));
111
+ }
112
+
113
+ if (SANITY_LAYOUT_TAGS.includes(body?._type)) {
114
+ revalidateTag(body._type, "max");
115
+ }
116
+
117
+ if (body._type === SANITY_REDIRECTS_TAG) {
118
+ revalidateTag(SANITY_REDIRECTS_TAG, "max");
119
+ }
120
+
121
+ return NextResponse.json({ status: 200, now: Date.now() });
122
+ } catch (error) {
123
+ console.error("Revalidation error:", error);
124
+ return new Response("Internal Server Error", { status: 500 });
125
+ }
126
+ }
@@ -0,0 +1,40 @@
1
+ import { ALL_BLOG_ARTICLES_QUERY } from "@/lib/integrations/sanity/queries";
2
+ import { generateRssFeed, rssErrorResponse } from "@/lib/utils/rss";
3
+ import { getPageUrl, getSitemapBaseUrl } from "@/lib/utils/sitemap";
4
+
5
+ export const dynamic = "force-dynamic";
6
+
7
+ export async function GET() {
8
+ const baseUrl = getSitemapBaseUrl();
9
+
10
+ try {
11
+ const sanityModule = await import("@/lib/integrations/sanity/client");
12
+ const client = sanityModule?.client;
13
+ if (!client) return rssErrorResponse();
14
+
15
+ const articles = await client.fetch(ALL_BLOG_ARTICLES_QUERY);
16
+
17
+ const items = (articles ?? [])
18
+ .filter((article) => article.resolvedSlug && !article.noIndex)
19
+ .map((article) => ({
20
+ title: article.title ?? "Untitled",
21
+ link: getPageUrl(baseUrl, article.resolvedSlug),
22
+ description: article.metadata?.metaDescription ?? undefined,
23
+ pubDate: article.blogContent?.date ?? undefined,
24
+ authors: article.blogContent?.author?.name
25
+ ? [article.blogContent.author.name]
26
+ : undefined,
27
+ }));
28
+
29
+ return generateRssFeed({
30
+ title: `${new URL(baseUrl).hostname} Blog`,
31
+ link: `${baseUrl}/blog`,
32
+ description: "Latest articles",
33
+ feedUrl: `${baseUrl}/feed.xml`,
34
+ items,
35
+ });
36
+ } catch (error) {
37
+ console.error("Error generating RSS feed:", error);
38
+ return rssErrorResponse();
39
+ }
40
+ }
@@ -0,0 +1,124 @@
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 {
5
+ getMarkdownPath,
6
+ getSitemap,
7
+ getSitemapBaseUrl,
8
+ } from "@/lib/utils/sitemap";
9
+
10
+ export const dynamic = "force-dynamic";
11
+
12
+ type PageDoc = PAGE_SITEMAP_QUERY_RESULT[number];
13
+
14
+ const ROOT_GROUP = "__root__";
15
+
16
+ const getFolderGroup = (slug: string | null) => {
17
+ if (!slug) return ROOT_GROUP;
18
+
19
+ const segments = slug.split("/").filter(Boolean);
20
+
21
+ if (segments.length <= 1) return ROOT_GROUP;
22
+
23
+ return segments.slice(0, -1).join("/");
24
+ };
25
+
26
+ const getGroupHeading = (group: string) => {
27
+ if (group === ROOT_GROUP) return "### Root Pages";
28
+
29
+ return `### /${group}`;
30
+ };
31
+
32
+ const sortPages = (left: PageDoc, right: PageDoc) => {
33
+ const leftLabel = left.title || left.slug || "Homepage";
34
+ const rightLabel = right.title || right.slug || "Homepage";
35
+
36
+ return leftLabel.localeCompare(rightLabel);
37
+ };
38
+
39
+ export const GET = async () => {
40
+ const baseUrl = getSitemapBaseUrl();
41
+
42
+ if (!isSanityConfigured()) {
43
+ return new NextResponse(
44
+ [
45
+ "# Sitemap",
46
+ "",
47
+ "## Pages",
48
+ "",
49
+ `- [Homepage](${baseUrl}/index.md)`,
50
+ "",
51
+ "CMS not configured. No additional content available.",
52
+ ].join("\n"),
53
+ {
54
+ headers: { "Content-Type": "text/markdown; charset=utf-8" },
55
+ },
56
+ );
57
+ }
58
+
59
+ try {
60
+ const allPages = await getSitemap();
61
+
62
+ if (!allPages) {
63
+ return new NextResponse("# Sitemap\n\nUnable to connect to CMS.", {
64
+ status: 503,
65
+ headers: { "Content-Type": "text/markdown; charset=utf-8" },
66
+ });
67
+ }
68
+
69
+ const pages = (allPages ?? []).filter((page) => !page.noIndex);
70
+ const pageGroups = new Map<string, PageDoc[]>();
71
+
72
+ for (const page of pages) {
73
+ const group = getFolderGroup(page.slug ?? null);
74
+ const groupedPages = pageGroups.get(group) ?? [];
75
+
76
+ groupedPages.push(page);
77
+ pageGroups.set(group, groupedPages);
78
+ }
79
+
80
+ const sortedGroupNames = [...pageGroups.keys()].sort((left, right) => {
81
+ if (left === ROOT_GROUP) return -1;
82
+ if (right === ROOT_GROUP) return 1;
83
+
84
+ return left.localeCompare(right);
85
+ });
86
+
87
+ const parts = [
88
+ `# ${new URL(baseUrl).hostname} - Content Sitemap`,
89
+ "",
90
+ "> This sitemap lists all available markdown content for AI agents.",
91
+ "",
92
+ "## Pages",
93
+ "",
94
+ ];
95
+
96
+ if (pages.length > 0) {
97
+ for (const groupName of sortedGroupNames) {
98
+ const groupedPages = (pageGroups.get(groupName) ?? []).sort(sortPages);
99
+
100
+ if (groupedPages.length === 0) continue;
101
+
102
+ parts.push(getGroupHeading(groupName), "");
103
+
104
+ for (const page of groupedPages) {
105
+ parts.push(
106
+ `- [${page.title || page.slug || "Homepage"}](${getMarkdownPath(baseUrl, page.slug ?? null)})`,
107
+ );
108
+ }
109
+
110
+ parts.push("");
111
+ }
112
+ }
113
+
114
+ if (pages.length === 0) parts.push("No content published yet.", "");
115
+
116
+ return new NextResponse(parts.join("\n"));
117
+ } catch (error) {
118
+ console.error("Error generating sitemap.md:", error);
119
+ return new NextResponse("# Sitemap\n\nError fetching content.", {
120
+ status: 500,
121
+ headers: { "Content-Type": "text/markdown; charset=utf-8" },
122
+ });
123
+ }
124
+ };
@@ -0,0 +1,8 @@
1
+ "use client";
2
+
3
+ import { NextStudio } from "next-sanity/studio";
4
+ import config from "@/lib/integrations/sanity/sanity.config";
5
+
6
+ const StudioPage = () => <NextStudio config={config} />;
7
+
8
+ export default StudioPage;
@@ -0,0 +1,27 @@
1
+ "use client";
2
+
3
+ import { useRouter } from "next/navigation";
4
+ import { useTransition } from "react";
5
+
6
+ export function DraftModeToggle() {
7
+ const router = useRouter();
8
+ const [isPending, startTransition] = useTransition();
9
+
10
+ const handleDisable = () => {
11
+ startTransition(async () => {
12
+ await fetch("/api/draft-mode/disable");
13
+ router.refresh();
14
+ });
15
+ };
16
+
17
+ return (
18
+ <button
19
+ type="button"
20
+ onClick={handleDisable}
21
+ disabled={isPending}
22
+ className="fixed bottom-4 left-1/2 z-50 -translate-x-1/2 rounded-full border border-white/10 bg-black/80 px-2 py-1 font-medium text-sm text-white shadow-lg backdrop-blur-sm transition-colors hover:bg-black/90 disabled:opacity-50"
23
+ >
24
+ {isPending ? "Disabling..." : "Disable Draft Mode"}
25
+ </button>
26
+ );
27
+ }
@@ -0,0 +1,87 @@
1
+ import { PortableText, type PortableTextProps } from "@portabletext/react";
2
+ import { Link } from "@/components/ui/link";
3
+ import { SanityImage } from "@/components/ui/sanity-image";
4
+ import {
5
+ getLinkAttributes,
6
+ urlForReference,
7
+ } from "../../lib/integrations/sanity/utils/link";
8
+
9
+ interface RichTextProps {
10
+ content: PortableTextProps["value"];
11
+ }
12
+
13
+ type LinkMarkProps = {
14
+ children: React.ReactNode;
15
+ value?: unknown;
16
+ };
17
+
18
+ const LinkMark = ({ children, value }: LinkMarkProps) => {
19
+ const href = urlForReference(value as Parameters<typeof urlForReference>[0]);
20
+ const attrs = getLinkAttributes(
21
+ value as Parameters<typeof getLinkAttributes>[0],
22
+ );
23
+
24
+ return (
25
+ <Link
26
+ href={href}
27
+ target={attrs.target}
28
+ rel={attrs.rel}
29
+ data-sanity-edit-target
30
+ >
31
+ {children}
32
+ </Link>
33
+ );
34
+ };
35
+
36
+ export const RichText = ({ content }: RichTextProps) => {
37
+ if (!content) return null;
38
+
39
+ return (
40
+ <PortableText
41
+ value={content}
42
+ components={{
43
+ types: {
44
+ image: ({ value }) => <SanityImage image={value} maxWidth={1920} />,
45
+ table: ({ value }) => {
46
+ const rows = value?.rows as
47
+ | Array<{ _key?: string; cells?: string[] }>
48
+ | undefined;
49
+ if (!rows?.length) return null;
50
+ return (
51
+ <table>
52
+ <tbody>
53
+ {rows.map((row, i) => (
54
+ <tr key={row._key ?? `row-${i}`}>
55
+ {row.cells?.map((cell) => {
56
+ const cellKey = `${row._key}-${cell}`;
57
+ return i === 0 ? (
58
+ <th key={cellKey}>{cell}</th>
59
+ ) : (
60
+ <td key={cellKey}>{cell}</td>
61
+ );
62
+ })}
63
+ </tr>
64
+ ))}
65
+ </tbody>
66
+ </table>
67
+ );
68
+ },
69
+ },
70
+ marks: {
71
+ link: LinkMark,
72
+ pageReference: LinkMark,
73
+ externalLink: LinkMark,
74
+ },
75
+ block: {
76
+ h1: ({ children }) => <h1 className="h1">{children}</h1>,
77
+ h2: ({ children }) => <h2 className="h2">{children}</h2>,
78
+ h3: ({ children }) => <h3 className="h3">{children}</h3>,
79
+ h4: ({ children }) => <h4 className="h4">{children}</h4>,
80
+ h5: ({ children }) => <h5 className="h5">{children}</h5>,
81
+ h6: ({ children }) => <h6 className="h6">{children}</h6>,
82
+ normal: ({ children }) => <p className="p">{children}</p>,
83
+ },
84
+ }}
85
+ />
86
+ );
87
+ };
@@ -0,0 +1,28 @@
1
+ import { draftMode } from "next/headers";
2
+ import { VisualEditing } from "next-sanity/visual-editing";
3
+ import { Suspense } from "react";
4
+ import { refreshAction } from "@/app/actions/refresh";
5
+ import { DraftModeToggle } from "@/components/sanity/draft-mode-toggle";
6
+ import { isSanityConfigured } from "@/lib/integrations/check-integration";
7
+ import { SanityLive } from "@/lib/integrations/sanity/live";
8
+
9
+ async function VisualEditingInner() {
10
+ const { isEnabled: isDraftMode } = await draftMode();
11
+ const sanityConfigured = isSanityConfigured();
12
+
13
+ if (!(sanityConfigured && isDraftMode)) return null;
14
+
15
+ return (
16
+ <>
17
+ <DraftModeToggle />
18
+ <VisualEditing />
19
+ <SanityLive action={refreshAction} />
20
+ </>
21
+ );
22
+ }
23
+
24
+ export const SanityVisualEditing = () => (
25
+ <Suspense fallback={null}>
26
+ <VisualEditingInner />
27
+ </Suspense>
28
+ );
@@ -0,0 +1,41 @@
1
+ import { getImageDimensions } from "@sanity/asset-utils";
2
+ import { Image, type ImageProps } from "@/components/ui/image";
3
+ import { urlForImage } from "@/lib/integrations/sanity/utils/image";
4
+
5
+ interface SanityImageProps extends Omit<ImageProps, "src" | "aspectRatio"> {
6
+ image: {
7
+ asset?: {
8
+ _ref: string;
9
+ _type: "reference";
10
+ };
11
+ alt?: string;
12
+ hotspot?: object;
13
+ crop?: object;
14
+ };
15
+ maxWidth?: number;
16
+ }
17
+
18
+ export function SanityImage({
19
+ image,
20
+ maxWidth = 1920,
21
+ alt,
22
+ fill,
23
+ ...props
24
+ }: SanityImageProps) {
25
+ if (!image?.asset) return null;
26
+
27
+ const { width, height } = getImageDimensions(image.asset);
28
+ const aspectRatio = width / height;
29
+
30
+ const imageProps = fill ? { fill: true } : { width, height };
31
+
32
+ return (
33
+ <Image
34
+ src={urlForImage(image).width(maxWidth).url()}
35
+ alt={alt || image.alt || ""}
36
+ aspectRatio={aspectRatio}
37
+ {...imageProps}
38
+ {...props}
39
+ />
40
+ );
41
+ }
@@ -0,0 +1,5 @@
1
+ export const isSanityConfigured = () =>
2
+ Boolean(
3
+ process.env.NEXT_PUBLIC_SANITY_PROJECT_ID &&
4
+ process.env.NEXT_PUBLIC_SANITY_DATASET,
5
+ );
@@ -0,0 +1,27 @@
1
+ import { createClient, type SanityClient } from "next-sanity";
2
+ import { isSanityConfigured } from "@/lib/integrations/check-integration";
3
+ import { apiVersion, dataset, projectId, sanityToken, studioUrl } from "./env";
4
+
5
+ /**
6
+ * Sanity client instance
7
+ *
8
+ * Returns null if Sanity is not configured (missing env vars).
9
+ * Always check with isSanityConfigured() before using.
10
+ */
11
+ export const client: SanityClient | null = isSanityConfigured()
12
+ ? createClient({
13
+ projectId,
14
+ dataset,
15
+ apiVersion,
16
+ useCdn: true,
17
+ perspective: "published",
18
+ token: sanityToken,
19
+ stega: {
20
+ studioUrl,
21
+ filter: (props) => {
22
+ if (props.sourcePath.at(-1) === "title") return true;
23
+ return props.filterDefault(props);
24
+ },
25
+ },
26
+ })
27
+ : null;
@@ -0,0 +1,23 @@
1
+ "use client";
2
+
3
+ import Link from "next/link";
4
+ import { usePathname } from "next/navigation";
5
+ import { useIsPresentationTool } from "next-sanity/hooks";
6
+
7
+ export function DisableDraftMode() {
8
+ const isPresentationTool = useIsPresentationTool();
9
+ const pathname = usePathname();
10
+
11
+ if (isPresentationTool) return null;
12
+ if (pathname.startsWith("/studio")) return null;
13
+
14
+ return (
15
+ <Link
16
+ href="/api/draft-mode/disable"
17
+ scroll={false}
18
+ className="dr-p-4 fixed top-safe right-safe z-50 bg-red font-mono text-primary text-sm uppercase"
19
+ >
20
+ Disable Draft Mode
21
+ </Link>
22
+ );
23
+ }
@@ -0,0 +1,39 @@
1
+ import { type ArrayOfObjectsInputProps, useFormValue } from "sanity";
2
+ import {
3
+ getVisiblePageBuilderInsertMenuGroups,
4
+ getVisiblePageBuilderReferenceMembers,
5
+ type PageBuilderGroupName,
6
+ } from "@/lib/integrations/sanity/page-builder-config";
7
+
8
+ export function PageBuilderInput(props: ArrayOfObjectsInputProps) {
9
+ const pageTypeValue = useFormValue(["type"]);
10
+ const pageType =
11
+ typeof pageTypeValue === "string"
12
+ ? (pageTypeValue as PageBuilderGroupName)
13
+ : undefined;
14
+
15
+ const visibleMemberNames = new Set(
16
+ getVisiblePageBuilderReferenceMembers(pageType).map(
17
+ (member) => member.name,
18
+ ),
19
+ );
20
+
21
+ return props.renderDefault({
22
+ ...props,
23
+ schemaType: {
24
+ ...props.schemaType,
25
+ of: props.schemaType.of.filter(
26
+ (member) =>
27
+ typeof member.name === "string" &&
28
+ visibleMemberNames.has(member.name),
29
+ ),
30
+ options: {
31
+ ...props.schemaType.options,
32
+ insertMenu: {
33
+ ...props.schemaType.options?.insertMenu,
34
+ groups: getVisiblePageBuilderInsertMenuGroups(pageType),
35
+ },
36
+ },
37
+ },
38
+ });
39
+ }
@@ -0,0 +1,50 @@
1
+ import { Select } from "@sanity/ui";
2
+ import { useEffect } from "react";
3
+ import { type StringInputProps, set, setIfMissing } from "sanity";
4
+
5
+ type PageCategoryOption = {
6
+ title: string;
7
+ value: string;
8
+ };
9
+
10
+ const isPageCategoryOption = (option: unknown): option is PageCategoryOption =>
11
+ typeof option === "object" &&
12
+ option !== null &&
13
+ "title" in option &&
14
+ "value" in option &&
15
+ typeof option.title === "string" &&
16
+ typeof option.value === "string";
17
+
18
+ export function PageCategoryInput(props: StringInputProps) {
19
+ useEffect(() => {
20
+ if (props.value === undefined) {
21
+ props.onChange(setIfMissing("generic"));
22
+ }
23
+ }, [props.onChange, props.value]);
24
+
25
+ const options = Array.isArray(props.schemaType.options?.list)
26
+ ? props.schemaType.options.list.filter(isPageCategoryOption)
27
+ : [];
28
+
29
+ if (options.length === 0) {
30
+ return props.renderDefault({ ...props, value: props.value ?? "generic" });
31
+ }
32
+
33
+ return (
34
+ <Select
35
+ {...props.elementProps}
36
+ disabled={props.readOnly}
37
+ onChange={(event) => props.onChange(set(event.currentTarget.value))}
38
+ value={props.value ?? "generic"}
39
+ {...(props.validationError
40
+ ? { customValidity: props.validationError }
41
+ : {})}
42
+ >
43
+ {options.map((option) => (
44
+ <option key={option.value} value={option.value}>
45
+ {option.title}
46
+ </option>
47
+ ))}
48
+ </Select>
49
+ );
50
+ }