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,521 @@
1
+ import type {
2
+ Article,
3
+ BreadcrumbList,
4
+ Event as EventSchema,
5
+ FAQPage,
6
+ ItemList,
7
+ JobPosting,
8
+ Organization,
9
+ SoftwareApplication,
10
+ Thing,
11
+ VideoObject,
12
+ WebPage,
13
+ WebSite,
14
+ WithContext,
15
+ } from "schema-dts";
16
+ import { getBaseUrl } from "./base-url";
17
+
18
+ const BASE_URL = getBaseUrl();
19
+
20
+ interface JsonLdProps<T extends Thing> {
21
+ data: WithContext<T>;
22
+ }
23
+
24
+ const serializeJsonLd = <T extends Thing>(data: WithContext<T>) =>
25
+ JSON.stringify(data).replace(/</g, "\\u003c");
26
+
27
+ export const JsonLd = <T extends Thing>({ data }: JsonLdProps<T>) => (
28
+ <script
29
+ type="application/ld+json"
30
+ dangerouslySetInnerHTML={{ __html: serializeJsonLd(data) }}
31
+ />
32
+ );
33
+
34
+ interface WebSiteJsonLdOptions {
35
+ name: string;
36
+ url?: string | undefined;
37
+ description?: string | undefined;
38
+ }
39
+
40
+ export const generateWebSiteJsonLd = (
41
+ options: WebSiteJsonLdOptions,
42
+ ): WithContext<WebSite> => {
43
+ const { name, url = BASE_URL, description } = options;
44
+ return {
45
+ "@context": "https://schema.org",
46
+ "@type": "WebSite",
47
+ name,
48
+ ...(url ? { url } : {}),
49
+ ...(description && { description }),
50
+ };
51
+ };
52
+
53
+ // Sanity asset URLs are already absolute (cdn.sanity.io); only prefix the
54
+ // origin for app-relative paths like "/logo.svg".
55
+ const toAbsoluteUrl = (value: string) =>
56
+ /^https?:\/\//.test(value) ? value : `${BASE_URL}${value}`;
57
+
58
+ interface OrganizationContactPoint {
59
+ email?: string | null;
60
+ telephone?: string | null;
61
+ contactType?: string | null;
62
+ }
63
+
64
+ interface OrganizationAddress {
65
+ streetAddress?: string | null;
66
+ locality?: string | null;
67
+ region?: string | null;
68
+ postalCode?: string | null;
69
+ country?: string | null;
70
+ }
71
+
72
+ interface OrganizationJsonLdOptions {
73
+ name: string;
74
+ url?: string | undefined;
75
+ logo?: string | undefined;
76
+ description?: string | undefined;
77
+ sameAs?: string[] | undefined;
78
+ legalName?: string | undefined;
79
+ contactPoint?: OrganizationContactPoint | null | undefined;
80
+ address?: OrganizationAddress | null | undefined;
81
+ }
82
+
83
+ export const generateOrganizationJsonLd = (
84
+ options: OrganizationJsonLdOptions,
85
+ ): WithContext<Organization> => {
86
+ const {
87
+ name,
88
+ url = BASE_URL,
89
+ logo,
90
+ description,
91
+ sameAs,
92
+ legalName,
93
+ contactPoint,
94
+ address,
95
+ } = options;
96
+
97
+ const contactPointNode =
98
+ contactPoint &&
99
+ (contactPoint.email || contactPoint.telephone || contactPoint.contactType)
100
+ ? {
101
+ "@type": "ContactPoint" as const,
102
+ ...(contactPoint.email && { email: contactPoint.email }),
103
+ ...(contactPoint.telephone && {
104
+ telephone: contactPoint.telephone,
105
+ }),
106
+ ...(contactPoint.contactType && {
107
+ contactType: contactPoint.contactType,
108
+ }),
109
+ }
110
+ : undefined;
111
+
112
+ const addressNode =
113
+ address &&
114
+ (address.streetAddress ||
115
+ address.locality ||
116
+ address.region ||
117
+ address.postalCode ||
118
+ address.country)
119
+ ? {
120
+ "@type": "PostalAddress" as const,
121
+ ...(address.streetAddress && {
122
+ streetAddress: address.streetAddress,
123
+ }),
124
+ ...(address.locality && { addressLocality: address.locality }),
125
+ ...(address.region && { addressRegion: address.region }),
126
+ ...(address.postalCode && { postalCode: address.postalCode }),
127
+ ...(address.country && { addressCountry: address.country }),
128
+ }
129
+ : undefined;
130
+
131
+ return {
132
+ "@context": "https://schema.org",
133
+ "@type": "Organization",
134
+ name,
135
+ ...(url ? { url } : {}),
136
+ ...(logo && { logo: toAbsoluteUrl(logo) }),
137
+ ...(description && { description }),
138
+ ...(legalName && { legalName }),
139
+ ...(sameAs?.length && { sameAs }),
140
+ ...(contactPointNode && { contactPoint: contactPointNode }),
141
+ ...(addressNode && { address: addressNode }),
142
+ };
143
+ };
144
+
145
+ interface WebPageJsonLdOptions {
146
+ title: string;
147
+ url?: string;
148
+ description?: string;
149
+ image?: string;
150
+ datePublished?: string;
151
+ dateModified?: string;
152
+ }
153
+
154
+ export const generateWebPageJsonLd = (
155
+ options: WebPageJsonLdOptions,
156
+ ): WithContext<WebPage> => {
157
+ const { title, url, description, image, datePublished, dateModified } =
158
+ options;
159
+ const fullUrl = url ? `${BASE_URL}${url}` : BASE_URL;
160
+
161
+ return {
162
+ "@context": "https://schema.org",
163
+ "@type": "WebPage",
164
+ name: title,
165
+ ...(fullUrl ? { url: fullUrl } : {}),
166
+ ...(description && { description }),
167
+ ...(image && { image }),
168
+ ...(datePublished && { datePublished }),
169
+ ...(dateModified && { dateModified }),
170
+ };
171
+ };
172
+
173
+ interface ArticleJsonLdOptions {
174
+ title: string;
175
+ url?: string;
176
+ description?: string;
177
+ image?: string;
178
+ datePublished?: string;
179
+ dateModified?: string;
180
+ authorName?: string;
181
+ authorUrl?: string;
182
+ }
183
+
184
+ export function generateArticleJsonLd(
185
+ options: ArticleJsonLdOptions,
186
+ ): WithContext<Article> {
187
+ const {
188
+ title,
189
+ url,
190
+ description,
191
+ image,
192
+ datePublished,
193
+ dateModified,
194
+ authorName,
195
+ authorUrl,
196
+ } = options;
197
+ const fullUrl = url ? `${BASE_URL}${url}` : BASE_URL;
198
+ return {
199
+ "@context": "https://schema.org",
200
+ "@type": "Article",
201
+ headline: title,
202
+ ...(fullUrl ? { url: fullUrl } : {}),
203
+ ...(description && { description }),
204
+ ...(image && { image }),
205
+ ...(datePublished && { datePublished }),
206
+ ...(dateModified && { dateModified }),
207
+ ...(authorName && {
208
+ author: {
209
+ "@type": "Person",
210
+ name: authorName,
211
+ ...(authorUrl && { url: authorUrl }),
212
+ },
213
+ }),
214
+ };
215
+ }
216
+
217
+ interface BreadcrumbItem {
218
+ name: string;
219
+ url: string;
220
+ }
221
+
222
+ export function generateBreadcrumbJsonLd(
223
+ items: BreadcrumbItem[],
224
+ ): WithContext<BreadcrumbList> {
225
+ return {
226
+ "@context": "https://schema.org",
227
+ "@type": "BreadcrumbList",
228
+ itemListElement: items.map((item, index) => ({
229
+ "@type": "ListItem" as const,
230
+ position: index + 1,
231
+ name: item.name,
232
+ item: `${BASE_URL}${item.url}`,
233
+ })),
234
+ };
235
+ }
236
+
237
+ // Sanity helpers — flat options instead of document shapes, so they fit any type.
238
+
239
+ interface SanityArticleJsonLdOptions {
240
+ title: string;
241
+ url: string;
242
+ description?: string | null | undefined;
243
+ image?: string | null | undefined;
244
+ datePublished?: string | null | undefined;
245
+ dateModified?: string;
246
+ authorName?: string | null | undefined;
247
+ authorUrl?: string | null | undefined;
248
+ }
249
+
250
+ export function generateSanityArticleJsonLd(
251
+ options: SanityArticleJsonLdOptions,
252
+ ): WithContext<Article> {
253
+ const opts: ArticleJsonLdOptions = { title: options.title, url: options.url };
254
+ if (options.description) opts.description = options.description;
255
+ if (options.image) opts.image = options.image;
256
+ if (options.datePublished) opts.datePublished = options.datePublished;
257
+ if (options.dateModified) opts.dateModified = options.dateModified;
258
+ if (options.authorName) opts.authorName = options.authorName;
259
+ if (options.authorUrl) opts.authorUrl = options.authorUrl;
260
+ return generateArticleJsonLd(opts);
261
+ }
262
+
263
+ interface SanityWebPageJsonLdOptions {
264
+ title: string;
265
+ url: string;
266
+ description?: string | null | undefined;
267
+ image?: string | null | undefined;
268
+ datePublished?: string | null | undefined;
269
+ dateModified?: string;
270
+ }
271
+
272
+ export function generateSanityWebPageJsonLd(
273
+ options: SanityWebPageJsonLdOptions,
274
+ ): WithContext<WebPage> {
275
+ const opts: WebPageJsonLdOptions = { title: options.title, url: options.url };
276
+ if (options.description) opts.description = options.description;
277
+ if (options.image) opts.image = options.image;
278
+ if (options.datePublished) opts.datePublished = options.datePublished;
279
+ if (options.dateModified) opts.dateModified = options.dateModified;
280
+ return generateWebPageJsonLd(opts);
281
+ }
282
+
283
+ // Page-level generators for the schema.org blocks editors attach via
284
+ // `metadata.jsonLd`. Plain options keep them CMS-independent.
285
+
286
+ export interface FaqJsonLdItem {
287
+ question: string;
288
+ answer: string;
289
+ }
290
+
291
+ export function generateFaqJsonLd(
292
+ items: FaqJsonLdItem[],
293
+ ): WithContext<FAQPage> {
294
+ return {
295
+ "@context": "https://schema.org",
296
+ "@type": "FAQPage",
297
+ mainEntity: items.map((item) => ({
298
+ "@type": "Question" as const,
299
+ name: item.question,
300
+ acceptedAnswer: {
301
+ "@type": "Answer" as const,
302
+ text: item.answer,
303
+ },
304
+ })),
305
+ };
306
+ }
307
+
308
+ export interface JobPostingJsonLdOptions {
309
+ title: string;
310
+ description: string;
311
+ datePosted: string;
312
+ validThrough?: string | undefined;
313
+ employmentType?: string | undefined;
314
+ organizationName?: string | undefined;
315
+ organizationUrl?: string | undefined;
316
+ remote?: boolean | undefined;
317
+ locality?: string | undefined;
318
+ region?: string | undefined;
319
+ country?: string | undefined;
320
+ }
321
+
322
+ export function generateJobPostingJsonLd(
323
+ options: JobPostingJsonLdOptions,
324
+ ): WithContext<JobPosting> {
325
+ const {
326
+ title,
327
+ description,
328
+ datePosted,
329
+ validThrough,
330
+ employmentType,
331
+ organizationName,
332
+ organizationUrl,
333
+ remote,
334
+ locality,
335
+ region,
336
+ country,
337
+ } = options;
338
+
339
+ const hiringOrganization = organizationName
340
+ ? {
341
+ "@type": "Organization" as const,
342
+ name: organizationName,
343
+ ...(organizationUrl && { url: toAbsoluteUrl(organizationUrl) }),
344
+ }
345
+ : undefined;
346
+
347
+ const jobLocation =
348
+ locality || region || country
349
+ ? {
350
+ "@type": "Place" as const,
351
+ address: {
352
+ "@type": "PostalAddress" as const,
353
+ ...(locality && { addressLocality: locality }),
354
+ ...(region && { addressRegion: region }),
355
+ ...(country && { addressCountry: country }),
356
+ },
357
+ }
358
+ : undefined;
359
+
360
+ return {
361
+ "@context": "https://schema.org",
362
+ "@type": "JobPosting",
363
+ title,
364
+ description,
365
+ datePosted,
366
+ ...(validThrough && { validThrough }),
367
+ ...(employmentType && { employmentType }),
368
+ ...(hiringOrganization && { hiringOrganization }),
369
+ ...(remote && { jobLocationType: "TELECOMMUTE" }),
370
+ ...(jobLocation && { jobLocation }),
371
+ };
372
+ }
373
+
374
+ export interface SoftwareApplicationJsonLdOptions {
375
+ name: string;
376
+ description?: string | undefined;
377
+ applicationCategory?: string | undefined;
378
+ operatingSystem?: string | undefined;
379
+ price?: number | undefined;
380
+ currency?: string | undefined;
381
+ url?: string | undefined;
382
+ image?: string | undefined;
383
+ }
384
+
385
+ export function generateSoftwareApplicationJsonLd(
386
+ options: SoftwareApplicationJsonLdOptions,
387
+ ): WithContext<SoftwareApplication> {
388
+ const {
389
+ name,
390
+ description,
391
+ applicationCategory,
392
+ operatingSystem,
393
+ price,
394
+ currency,
395
+ url,
396
+ image,
397
+ } = options;
398
+
399
+ const offers =
400
+ typeof price === "number"
401
+ ? {
402
+ "@type": "Offer" as const,
403
+ price: price.toString(),
404
+ ...(currency && { priceCurrency: currency }),
405
+ }
406
+ : undefined;
407
+
408
+ return {
409
+ "@context": "https://schema.org",
410
+ "@type": "SoftwareApplication",
411
+ name,
412
+ ...(description && { description }),
413
+ ...(applicationCategory && { applicationCategory }),
414
+ ...(operatingSystem && { operatingSystem }),
415
+ ...(url && { url: toAbsoluteUrl(url) }),
416
+ ...(image && { image: toAbsoluteUrl(image) }),
417
+ ...(offers && { offers }),
418
+ };
419
+ }
420
+
421
+ export interface ItemListEntry {
422
+ name: string;
423
+ url?: string | undefined;
424
+ }
425
+
426
+ export interface ItemListJsonLdOptions {
427
+ name?: string | undefined;
428
+ items: ItemListEntry[];
429
+ }
430
+
431
+ export function generateItemListJsonLd(
432
+ options: ItemListJsonLdOptions,
433
+ ): WithContext<ItemList> {
434
+ const { name, items } = options;
435
+
436
+ return {
437
+ "@context": "https://schema.org",
438
+ "@type": "ItemList",
439
+ ...(name && { name }),
440
+ itemListElement: items.map((item, index) => ({
441
+ "@type": "ListItem" as const,
442
+ position: index + 1,
443
+ name: item.name,
444
+ ...(item.url && { url: toAbsoluteUrl(item.url) }),
445
+ })),
446
+ };
447
+ }
448
+
449
+ export interface EventJsonLdOptions {
450
+ name: string;
451
+ description?: string | undefined;
452
+ startDate: string;
453
+ endDate?: string | undefined;
454
+ locationName?: string | undefined;
455
+ locationAddress?: string | undefined;
456
+ url?: string | undefined;
457
+ image?: string | undefined;
458
+ }
459
+
460
+ export function generateEventJsonLd(
461
+ options: EventJsonLdOptions,
462
+ ): WithContext<EventSchema> {
463
+ const {
464
+ name,
465
+ description,
466
+ startDate,
467
+ endDate,
468
+ locationName,
469
+ locationAddress,
470
+ url,
471
+ image,
472
+ } = options;
473
+
474
+ const location =
475
+ locationName || locationAddress
476
+ ? {
477
+ "@type": "Place" as const,
478
+ ...(locationName && { name: locationName }),
479
+ ...(locationAddress && { address: locationAddress }),
480
+ }
481
+ : undefined;
482
+
483
+ return {
484
+ "@context": "https://schema.org",
485
+ "@type": "Event",
486
+ name,
487
+ startDate,
488
+ ...(endDate && { endDate }),
489
+ ...(description && { description }),
490
+ ...(url && { url: toAbsoluteUrl(url) }),
491
+ ...(image && { image: toAbsoluteUrl(image) }),
492
+ ...(location && { location }),
493
+ };
494
+ }
495
+
496
+ export interface VideoJsonLdOptions {
497
+ name: string;
498
+ description: string;
499
+ thumbnail: string;
500
+ uploadDate: string;
501
+ contentUrl?: string | undefined;
502
+ embedUrl?: string | undefined;
503
+ }
504
+
505
+ export function generateVideoJsonLd(
506
+ options: VideoJsonLdOptions,
507
+ ): WithContext<VideoObject> {
508
+ const { name, description, thumbnail, uploadDate, contentUrl, embedUrl } =
509
+ options;
510
+
511
+ return {
512
+ "@context": "https://schema.org",
513
+ "@type": "VideoObject",
514
+ name,
515
+ description,
516
+ thumbnailUrl: toAbsoluteUrl(thumbnail),
517
+ uploadDate,
518
+ ...(contentUrl && { contentUrl }),
519
+ ...(embedUrl && { embedUrl }),
520
+ };
521
+ }
@@ -0,0 +1,167 @@
1
+ import type { Metadata } from "next";
2
+ import { urlForImage } from "@/lib/integrations/sanity/utils/image";
3
+ import { getBaseUrl } from "./base-url";
4
+
5
+ interface GenerateMetadataOptions {
6
+ title?: string | undefined;
7
+ description?: string | undefined;
8
+ keywords?: string[] | undefined;
9
+ image?:
10
+ | {
11
+ url?: string | undefined;
12
+ width?: number | undefined;
13
+ height?: number | undefined;
14
+ }
15
+ | undefined;
16
+ url?: string | undefined;
17
+ siteName?: string | undefined;
18
+ index?: boolean | undefined;
19
+ noIndex?: boolean | undefined;
20
+ type?: "website" | "article";
21
+ publishedTime?: string | undefined;
22
+ modifiedTime?: string | undefined;
23
+ authors?: string[] | undefined;
24
+ }
25
+
26
+ interface SanityMetadataImage {
27
+ asset?: {
28
+ _ref?: string;
29
+ _type?: "reference";
30
+ };
31
+ }
32
+
33
+ interface SanityPageMetadata {
34
+ metaTitle?: string | null;
35
+ metaDescription?: string | null;
36
+ og?: {
37
+ image?: SanityMetadataImage | null;
38
+ } | null;
39
+ index?: boolean | null;
40
+ title?: string | null;
41
+ description?: string | null;
42
+ image?: SanityMetadataImage | null;
43
+ noIndex?: boolean | null;
44
+ }
45
+
46
+ interface SanityMetadataDocument {
47
+ title?: string | null;
48
+ subtitle?: string | null;
49
+ metadata?: SanityPageMetadata | null;
50
+ }
51
+
52
+ interface GenerateSanityMetadataOptions {
53
+ document: SanityMetadataDocument;
54
+ url?: string;
55
+ siteName?: string;
56
+ type?: "website" | "article";
57
+ }
58
+
59
+ const APP_BASE_URL = getBaseUrl();
60
+
61
+ /**
62
+ * Generate complete metadata object for pages
63
+ *
64
+ * @example
65
+ * ```ts
66
+ * export async function generateMetadata({ params }) {
67
+ * const page = await fetchPage(params.slug)
68
+ *
69
+ * return generatePageMetadata({
70
+ * title: page.metadata?.title || page.title,
71
+ * description: page.metadata?.description,
72
+ * image: { url: page.metadata?.image?.asset?.url },
73
+ * url: `/page/${params.slug}`,
74
+ * noIndex: page.metadata?.noIndex,
75
+ * })
76
+ * }
77
+ * ```
78
+ */
79
+ const generatePageMetadata = (options: GenerateMetadataOptions): Metadata => {
80
+ const {
81
+ title,
82
+ description,
83
+ keywords,
84
+ image,
85
+ url,
86
+ siteName = "Basement",
87
+ index,
88
+ noIndex = false,
89
+ type = "website",
90
+ publishedTime,
91
+ modifiedTime,
92
+ authors,
93
+ } = options;
94
+
95
+ const shouldIndex = index ?? !noIndex;
96
+ const fullUrl = url ? `${APP_BASE_URL}${url}` : APP_BASE_URL;
97
+ const imageUrl = image?.url || "/opengraph-image.jpg";
98
+ const width = image?.width || 1200;
99
+ const height = image?.height || 630;
100
+
101
+ const metadata: Metadata = {
102
+ metadataBase: new URL(APP_BASE_URL),
103
+ title,
104
+ description,
105
+ keywords,
106
+ alternates: { canonical: url || "/" },
107
+ openGraph: {
108
+ title,
109
+ description,
110
+ url: fullUrl,
111
+ siteName,
112
+ locale: "en_US",
113
+ type,
114
+ images: [{ url: imageUrl, width, height }],
115
+ ...(publishedTime && { publishedTime }),
116
+ ...(modifiedTime && { modifiedTime }),
117
+ ...(authors && { authors }),
118
+ },
119
+ twitter: {
120
+ card: "summary_large_image",
121
+ title,
122
+ description,
123
+ images: [{ url: imageUrl, width, height }],
124
+ },
125
+ other: { "fb:app_id": process.env.NEXT_PUBLIC_FACEBOOK_APP_ID || "" },
126
+ };
127
+
128
+ if (!shouldIndex) metadata.robots = { index: false, follow: false };
129
+
130
+ return metadata;
131
+ };
132
+
133
+ export const generateSanityMetadata = ({
134
+ document,
135
+ url,
136
+ siteName,
137
+ type = "website",
138
+ }: GenerateSanityMetadataOptions): Metadata => {
139
+ const pageMetadata = document.metadata;
140
+ const title =
141
+ pageMetadata?.metaTitle ||
142
+ pageMetadata?.title ||
143
+ document.title ||
144
+ undefined;
145
+ const description =
146
+ pageMetadata?.metaDescription ||
147
+ pageMetadata?.description ||
148
+ document.subtitle ||
149
+ undefined;
150
+ const socialImageSource = pageMetadata?.image || pageMetadata?.og?.image;
151
+ const imageUrl = socialImageSource
152
+ ? urlForImage(socialImageSource).width(1200).height(630).url()
153
+ : undefined;
154
+ const index =
155
+ pageMetadata?.index ??
156
+ (pageMetadata?.noIndex != null ? !pageMetadata.noIndex : true);
157
+
158
+ return generatePageMetadata({
159
+ title,
160
+ description,
161
+ image: imageUrl ? { url: imageUrl, width: 1200, height: 630 } : undefined,
162
+ url,
163
+ siteName,
164
+ index,
165
+ type,
166
+ });
167
+ };