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,31 @@
1
+ /**
2
+ * HubSpot integration configuration manifest.
3
+ * Single source of truth for dependencies, scripts, paths, and merge rules.
4
+ *
5
+ * The core is dependency-free: it loads HubSpot's official forms script at
6
+ * runtime and styles the injected DOM with self-contained CSS.
7
+ */
8
+ import type { IntegrationId } from "../../../../domain/integration.js";
9
+ /**
10
+ * A path copied only when another integration is already present in the target
11
+ * project. Used to ship the optional Sanity Studio form picker.
12
+ */
13
+ export interface ConditionalPath {
14
+ /** Source path relative to this integration's `files/` directory. */
15
+ src: string;
16
+ /** Destination path relative to the project root (before `src/` prefixing). */
17
+ dest: string;
18
+ /** Integration that must already exist for the copy to run. */
19
+ requires: IntegrationId;
20
+ }
21
+ export interface HubSpotIntegrationConfig {
22
+ name: string;
23
+ dependencies: Record<string, string>;
24
+ devDependencies: Record<string, string>;
25
+ scripts: Record<string, string>;
26
+ additivePaths: string[];
27
+ mergeFiles: string[];
28
+ conditionalPaths: ConditionalPath[];
29
+ }
30
+ export declare const hubspotConfig: HubSpotIntegrationConfig;
31
+ //# sourceMappingURL=config.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"config.d.ts","sourceRoot":"","sources":["../../../../../src/modules/features/forms/hubspot/config.ts"],"names":[],"mappings":"AAAA;;;;;;GAMG;AAEH,OAAO,KAAK,EAAE,aAAa,EAAE,MAAM,mCAAmC,CAAC;AAEvE;;;GAGG;AACH,MAAM,WAAW,eAAe;IAC/B,qEAAqE;IACrE,GAAG,EAAE,MAAM,CAAC;IACZ,+EAA+E;IAC/E,IAAI,EAAE,MAAM,CAAC;IACb,+DAA+D;IAC/D,QAAQ,EAAE,aAAa,CAAC;CACxB;AAED,MAAM,WAAW,wBAAwB;IACxC,IAAI,EAAE,MAAM,CAAC;IACb,YAAY,EAAE,MAAM,CAAC,MAAM,EAAE,MAAM,CAAC,CAAC;IACrC,eAAe,EAAE,MAAM,CAAC,MAAM,EAAE,MAAM,CAAC,CAAC;IACxC,OAAO,EAAE,MAAM,CAAC,MAAM,EAAE,MAAM,CAAC,CAAC;IAChC,aAAa,EAAE,MAAM,EAAE,CAAC;IACxB,UAAU,EAAE,MAAM,EAAE,CAAC;IACrB,gBAAgB,EAAE,eAAe,EAAE,CAAC;CACpC;AAED,eAAO,MAAM,aAAa,EAAE,wBAoB3B,CAAC"}
@@ -0,0 +1,25 @@
1
+ /**
2
+ * HubSpot integration configuration manifest.
3
+ * Single source of truth for dependencies, scripts, paths, and merge rules.
4
+ *
5
+ * The core is dependency-free: it loads HubSpot's official forms script at
6
+ * runtime and styles the injected DOM with self-contained CSS.
7
+ */
8
+ export const hubspotConfig = {
9
+ name: "hubspot",
10
+ // The core embed loads HubSpot's script at runtime — no extra packages.
11
+ dependencies: {},
12
+ devDependencies: {},
13
+ scripts: {},
14
+ additivePaths: ["lib/integrations/hubspot"],
15
+ mergeFiles: [],
16
+ // The Sanity Studio picker is only useful with Sanity installed.
17
+ conditionalPaths: [
18
+ {
19
+ src: "_sanity-adapter",
20
+ dest: "lib/integrations/hubspot/sanity",
21
+ requires: "sanity",
22
+ },
23
+ ],
24
+ };
25
+ //# sourceMappingURL=config.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"config.js","sourceRoot":"","sources":["../../../../../src/modules/features/forms/hubspot/config.ts"],"names":[],"mappings":"AAAA;;;;;;GAMG;AA2BH,MAAM,CAAC,MAAM,aAAa,GAA6B;IACtD,IAAI,EAAE,SAAS;IAEf,wEAAwE;IACxE,YAAY,EAAE,EAAE;IAChB,eAAe,EAAE,EAAE;IACnB,OAAO,EAAE,EAAE;IAEX,aAAa,EAAE,CAAC,0BAA0B,CAAC;IAE3C,UAAU,EAAE,EAAE;IAEd,iEAAiE;IACjE,gBAAgB,EAAE;QACjB;YACC,GAAG,EAAE,iBAAiB;YACtB,IAAI,EAAE,iCAAiC;YACvC,QAAQ,EAAE,QAAQ;SAClB;KACD;CACD,CAAC"}
package/dist/paths.d.ts CHANGED
@@ -4,6 +4,10 @@ export declare const TEMPLATES_DIR: string;
4
4
  export declare const TEMPLATE_HOOKS_DIR: string;
5
5
  /** Directory containing the Biome linting plugin rules. */
6
6
  export declare const PLUGINS_DIR: string;
7
+ /** Directory containing bundled agent skills copied into generated projects. */
8
+ export declare const AGENT_SKILLS_DIR: string;
9
+ /** Node-version guard injected into each scaffolded project; under `src/` so it ships verbatim. */
10
+ export declare const CHECK_NODE_VERSION_FILE: string;
7
11
  /**
8
12
  * Directory containing the Sanity integration source files.
9
13
  * Always resolves to `src/modules/features/cms/sanity/files/` relative to the
@@ -17,4 +21,16 @@ export declare const SANITY_FILES_DIR: string;
17
21
  * relative to the project root.
18
22
  */
19
23
  export declare const SANITY_PAGEBUILDER_FILES_DIR: string;
24
+ /**
25
+ * Directory containing the HubSpot integration source files.
26
+ * Always resolves to `src/modules/features/forms/hubspot/files/` relative to the
27
+ * project root.
28
+ */
29
+ export declare const HUBSPOT_FILES_DIR: string;
30
+ /**
31
+ * Directory containing the dotenvx integration source files (the CI guard
32
+ * workflow and the 1Password key-restore script). Always resolves to
33
+ * `src/modules/features/env/dotenvx/files/` relative to the project root.
34
+ */
35
+ export declare const DOTENVX_FILES_DIR: string;
20
36
  //# sourceMappingURL=paths.d.ts.map
@@ -1 +1 @@
1
- {"version":3,"file":"paths.d.ts","sourceRoot":"","sources":["../src/paths.ts"],"names":[],"mappings":"AAaA,6DAA6D;AAC7D,eAAO,MAAM,aAAa,EAAE,MAI3B,CAAC;AAEF,0DAA0D;AAC1D,eAAO,MAAM,kBAAkB,EAAE,MAIhC,CAAC;AAEF,2DAA2D;AAC3D,eAAO,MAAM,WAAW,EAAE,MAA2C,CAAC;AAEtE;;;;;GAKG;AACH,eAAO,MAAM,gBAAgB,EAAE,MAQ9B,CAAC;AAEF;;;;GAIG;AACH,eAAO,MAAM,4BAA4B,EAAE,MAQ1C,CAAC"}
1
+ {"version":3,"file":"paths.d.ts","sourceRoot":"","sources":["../src/paths.ts"],"names":[],"mappings":"AAaA,6DAA6D;AAC7D,eAAO,MAAM,aAAa,EAAE,MAI3B,CAAC;AAEF,0DAA0D;AAC1D,eAAO,MAAM,kBAAkB,EAAE,MAIhC,CAAC;AAEF,2DAA2D;AAC3D,eAAO,MAAM,WAAW,EAAE,MAA2C,CAAC;AAEtE,gFAAgF;AAChF,eAAO,MAAM,gBAAgB,EAAE,MAI9B,CAAC;AAEF,mGAAmG;AACnG,eAAO,MAAM,uBAAuB,EAAE,MAKrC,CAAC;AAEF;;;;;GAKG;AACH,eAAO,MAAM,gBAAgB,EAAE,MAQ9B,CAAC;AAEF;;;;GAIG;AACH,eAAO,MAAM,4BAA4B,EAAE,MAQ1C,CAAC;AAEF;;;;GAIG;AACH,eAAO,MAAM,iBAAiB,EAAE,MAQ/B,CAAC;AAEF;;;;GAIG;AACH,eAAO,MAAM,iBAAiB,EAAE,MAQ/B,CAAC"}
package/dist/paths.js CHANGED
@@ -14,6 +14,10 @@ export const TEMPLATES_DIR = path.join(PROJECT_ROOT, "src", "templates");
14
14
  export const TEMPLATE_HOOKS_DIR = path.join(PROJECT_ROOT, "src", "template-hooks");
15
15
  /** Directory containing the Biome linting plugin rules. */
16
16
  export const PLUGINS_DIR = path.join(PROJECT_ROOT, "plugins");
17
+ /** Directory containing bundled agent skills copied into generated projects. */
18
+ export const AGENT_SKILLS_DIR = path.join(PROJECT_ROOT, "src", "agent-skills");
19
+ /** Node-version guard injected into each scaffolded project; under `src/` so it ships verbatim. */
20
+ export const CHECK_NODE_VERSION_FILE = path.join(PROJECT_ROOT, "src", "assets", "check-node-version.mjs");
17
21
  /**
18
22
  * Directory containing the Sanity integration source files.
19
23
  * Always resolves to `src/modules/features/cms/sanity/files/` relative to the
@@ -27,4 +31,16 @@ export const SANITY_FILES_DIR = path.join(PROJECT_ROOT, "src", "modules", "featu
27
31
  * relative to the project root.
28
32
  */
29
33
  export const SANITY_PAGEBUILDER_FILES_DIR = path.join(PROJECT_ROOT, "src", "modules", "features", "cms", "sanity-pagebuilder", "files");
34
+ /**
35
+ * Directory containing the HubSpot integration source files.
36
+ * Always resolves to `src/modules/features/forms/hubspot/files/` relative to the
37
+ * project root.
38
+ */
39
+ export const HUBSPOT_FILES_DIR = path.join(PROJECT_ROOT, "src", "modules", "features", "forms", "hubspot", "files");
40
+ /**
41
+ * Directory containing the dotenvx integration source files (the CI guard
42
+ * workflow and the 1Password key-restore script). Always resolves to
43
+ * `src/modules/features/env/dotenvx/files/` relative to the project root.
44
+ */
45
+ export const DOTENVX_FILES_DIR = path.join(PROJECT_ROOT, "src", "modules", "features", "env", "dotenvx", "files");
30
46
  //# sourceMappingURL=paths.js.map
package/dist/paths.js.map CHANGED
@@ -1 +1 @@
1
- {"version":3,"file":"paths.js","sourceRoot":"","sources":["../src/paths.ts"],"names":[],"mappings":"AAAA,OAAO,IAAI,MAAM,WAAW,CAAC;AAC7B,OAAO,EAAE,aAAa,EAAE,MAAM,UAAU,CAAC;AAEzC,MAAM,UAAU,GAAW,aAAa,CAAC,MAAM,CAAC,IAAI,CAAC,GAAG,CAAC,CAAC;AAC1D,MAAM,SAAS,GAAW,IAAI,CAAC,OAAO,CAAC,UAAU,CAAC,CAAC;AAEnD;;;;GAIG;AACH,MAAM,YAAY,GAAW,IAAI,CAAC,OAAO,CAAC,SAAS,EAAE,IAAI,CAAC,CAAC;AAE3D,6DAA6D;AAC7D,MAAM,CAAC,MAAM,aAAa,GAAW,IAAI,CAAC,IAAI,CAC7C,YAAY,EACZ,KAAK,EACL,WAAW,CACX,CAAC;AAEF,0DAA0D;AAC1D,MAAM,CAAC,MAAM,kBAAkB,GAAW,IAAI,CAAC,IAAI,CAClD,YAAY,EACZ,KAAK,EACL,gBAAgB,CAChB,CAAC;AAEF,2DAA2D;AAC3D,MAAM,CAAC,MAAM,WAAW,GAAW,IAAI,CAAC,IAAI,CAAC,YAAY,EAAE,SAAS,CAAC,CAAC;AAEtE;;;;;GAKG;AACH,MAAM,CAAC,MAAM,gBAAgB,GAAW,IAAI,CAAC,IAAI,CAChD,YAAY,EACZ,KAAK,EACL,SAAS,EACT,UAAU,EACV,KAAK,EACL,QAAQ,EACR,OAAO,CACP,CAAC;AAEF;;;;GAIG;AACH,MAAM,CAAC,MAAM,4BAA4B,GAAW,IAAI,CAAC,IAAI,CAC5D,YAAY,EACZ,KAAK,EACL,SAAS,EACT,UAAU,EACV,KAAK,EACL,oBAAoB,EACpB,OAAO,CACP,CAAC"}
1
+ {"version":3,"file":"paths.js","sourceRoot":"","sources":["../src/paths.ts"],"names":[],"mappings":"AAAA,OAAO,IAAI,MAAM,WAAW,CAAC;AAC7B,OAAO,EAAE,aAAa,EAAE,MAAM,UAAU,CAAC;AAEzC,MAAM,UAAU,GAAW,aAAa,CAAC,MAAM,CAAC,IAAI,CAAC,GAAG,CAAC,CAAC;AAC1D,MAAM,SAAS,GAAW,IAAI,CAAC,OAAO,CAAC,UAAU,CAAC,CAAC;AAEnD;;;;GAIG;AACH,MAAM,YAAY,GAAW,IAAI,CAAC,OAAO,CAAC,SAAS,EAAE,IAAI,CAAC,CAAC;AAE3D,6DAA6D;AAC7D,MAAM,CAAC,MAAM,aAAa,GAAW,IAAI,CAAC,IAAI,CAC7C,YAAY,EACZ,KAAK,EACL,WAAW,CACX,CAAC;AAEF,0DAA0D;AAC1D,MAAM,CAAC,MAAM,kBAAkB,GAAW,IAAI,CAAC,IAAI,CAClD,YAAY,EACZ,KAAK,EACL,gBAAgB,CAChB,CAAC;AAEF,2DAA2D;AAC3D,MAAM,CAAC,MAAM,WAAW,GAAW,IAAI,CAAC,IAAI,CAAC,YAAY,EAAE,SAAS,CAAC,CAAC;AAEtE,gFAAgF;AAChF,MAAM,CAAC,MAAM,gBAAgB,GAAW,IAAI,CAAC,IAAI,CAChD,YAAY,EACZ,KAAK,EACL,cAAc,CACd,CAAC;AAEF,mGAAmG;AACnG,MAAM,CAAC,MAAM,uBAAuB,GAAW,IAAI,CAAC,IAAI,CACvD,YAAY,EACZ,KAAK,EACL,QAAQ,EACR,wBAAwB,CACxB,CAAC;AAEF;;;;;GAKG;AACH,MAAM,CAAC,MAAM,gBAAgB,GAAW,IAAI,CAAC,IAAI,CAChD,YAAY,EACZ,KAAK,EACL,SAAS,EACT,UAAU,EACV,KAAK,EACL,QAAQ,EACR,OAAO,CACP,CAAC;AAEF;;;;GAIG;AACH,MAAM,CAAC,MAAM,4BAA4B,GAAW,IAAI,CAAC,IAAI,CAC5D,YAAY,EACZ,KAAK,EACL,SAAS,EACT,UAAU,EACV,KAAK,EACL,oBAAoB,EACpB,OAAO,CACP,CAAC;AAEF;;;;GAIG;AACH,MAAM,CAAC,MAAM,iBAAiB,GAAW,IAAI,CAAC,IAAI,CACjD,YAAY,EACZ,KAAK,EACL,SAAS,EACT,UAAU,EACV,OAAO,EACP,SAAS,EACT,OAAO,CACP,CAAC;AAEF;;;;GAIG;AACH,MAAM,CAAC,MAAM,iBAAiB,GAAW,IAAI,CAAC,IAAI,CACjD,YAAY,EACZ,KAAK,EACL,SAAS,EACT,UAAU,EACV,KAAK,EACL,SAAS,EACT,OAAO,CACP,CAAC"}
package/package.json CHANGED
@@ -1,9 +1,12 @@
1
1
  {
2
2
  "name": "bsmnt",
3
- "version": "0.4.3",
4
- "packageManager": "bun@1.2.20",
3
+ "version": "0.6.0",
4
+ "packageManager": "bun@1.3.14",
5
5
  "description": "CLI to scaffold basement projects and add integrations",
6
6
  "type": "module",
7
+ "engines": {
8
+ "node": ">=24"
9
+ },
7
10
  "workspaces": [
8
11
  ".",
9
12
  "src/templates/*"
@@ -20,12 +23,18 @@
20
23
  "!src/templates/*/.next",
21
24
  "!src/templates/*/bun.lock",
22
25
  "src/template-hooks",
26
+ "src/agent-skills",
23
27
  "src/modules/features/cms/sanity/files",
28
+ "src/modules/features/cms/sanity-pagebuilder/files",
29
+ "src/modules/features/env/dotenvx/files",
30
+ "src/assets",
24
31
  "plugins"
25
32
  ],
26
33
  "scripts": {
34
+ "preinstall": "node src/assets/check-node-version.mjs",
27
35
  "build": "tsc",
28
36
  "check": "tsc --noEmit",
37
+ "test": "bun test src",
29
38
  "dev": "tsx src/index.ts",
30
39
  "lint": "biome check .",
31
40
  "lint:fix": "biome check --write --unsafe .",
@@ -51,7 +60,9 @@
51
60
  "lint:all": "turbo run lint --filter=bsmnt-next-starter --filter=bsmnt-webgpu-starter --filter=bsmnt-experiments-starter",
52
61
  "version-canary": "changeset version --snapshot canary",
53
62
  "release-canary": "changeset publish --tag canary --no-git-tag",
54
- "test:templates": "bash scripts/test-templates.sh"
63
+ "test:templates": "bash scripts/test-templates.sh",
64
+ "link:local": "bash scripts/build-and-link.sh",
65
+ "unlink:local": "bash scripts/build-and-link.sh --unlink"
55
66
  },
56
67
  "keywords": [
57
68
  "webgpu",
@@ -63,23 +74,24 @@
63
74
  "author": "",
64
75
  "license": "ISC",
65
76
  "dependencies": {
66
- "@sanity/client": "^7.15.0",
67
- "@sanity/image-url": "^2.0.3",
68
- "@sanity/vision": "^5.11.0",
77
+ "@sanity/client": "^7.22.1",
78
+ "@sanity/image-url": "^2.1.1",
79
+ "@sanity/vision": "^5.30.0",
69
80
  "commander": "^13.1.0",
81
+ "dotenv": "^17.4.2",
70
82
  "fs-extra": "^11.3.3",
71
- "next-sanity": "^12.1.0",
83
+ "next-sanity": "^13.0.10",
72
84
  "ora": "^9.1.0",
73
85
  "picocolors": "^1.1.1",
74
86
  "prompts": "^2.4.2",
75
- "sanity": "^5.11.0"
87
+ "sanity": "^5.30.0"
76
88
  },
77
89
  "devDependencies": {
78
90
  "@biomejs/biome": "^2.4.14",
79
91
  "@changesets/changelog-github": "^0.5.2",
80
92
  "@changesets/cli": "^2.29.8",
81
93
  "@types/fs-extra": "^11.0.4",
82
- "@types/node": "^25.2.3",
94
+ "@types/node": "^24.13.1",
83
95
  "@types/prompts": "^2.4.9",
84
96
  "cross-env": "^10.1.0",
85
97
  "lefthook": "^1.13.0",
@@ -0,0 +1,85 @@
1
+ ---
2
+ name: json-ld
3
+ description: >-
4
+ Add schema.org JSON-LD structured data when building components or pages
5
+ that render structured content (job listings, FAQs, events, videos,
6
+ work/product lists, articles, breadcrumbs). Use BEFORE hand-writing any
7
+ <script type="application/ld+json"> tag — this project ships typed
8
+ generators for all common schemas.
9
+ ---
10
+
11
+ # JSON-LD Structured Data
12
+
13
+ This project ships typed JSON-LD generators and a `<JsonLd />` component in
14
+ `lib/utils/json-ld.tsx`. Never hand-roll a `<script type="application/ld+json">`
15
+ tag — import a generator and render it through `<JsonLd />` (it serializes and
16
+ escapes `<` for safe embedding).
17
+
18
+ ```tsx
19
+ import { JsonLd, generateJobPostingJsonLd } from "@/lib/utils/json-ld"
20
+
21
+ // inside a server component
22
+ {jobs.map((job) => (
23
+ <JsonLd
24
+ key={job.id}
25
+ data={generateJobPostingJsonLd({
26
+ title: job.title,
27
+ description: job.description,
28
+ datePosted: job.publishedAt,
29
+ employmentType: job.type,
30
+ organizationName: job.company,
31
+ })}
32
+ />
33
+ ))}
34
+ ```
35
+
36
+ ## Which tier owns the data?
37
+
38
+ 1. **Site-wide identity** (`Organization`, `WebSite`) — already emitted by the
39
+ site layout. Never re-emit these from components or pages. With Sanity,
40
+ editors manage it in Studio → Layout → Structured Data; Company Data social
41
+ links merge into `sameAs` via their "Include in Structured Data" toggle.
42
+ 2. **Page-level** (Sanity projects only) — editors attach predefined JSON-LD
43
+ blocks (FAQ, Event, Video, Job Posting, Software Application, Item List,
44
+ Custom) to a page under SEO & Metadata → Structured Data (JSON-LD). The
45
+ page document component already renders them. If the content is page-owned
46
+ and editor-managed, point editors to that field instead of coding it.
47
+ 3. **Component-level** — when a component itself renders structured content
48
+ (from props or CMS data), emit JSON-LD next to the markup using the
49
+ generators below.
50
+
51
+ ## Component → schema mapping
52
+
53
+ | Component renders | Generator |
54
+ | ------------------------------------------ | ------------------------------------ |
55
+ | Job / career listings | `generateJobPostingJsonLd` (per job) |
56
+ | FAQ / accordion Q&A | `generateFaqJsonLd` |
57
+ | Events / agenda | `generateEventJsonLd` (per event) |
58
+ | Video player or embed | `generateVideoJsonLd` |
59
+ | Curated lists (work, products, post index) | `generateItemListJsonLd` |
60
+ | Article / blog post body | `generateArticleJsonLd` |
61
+ | Breadcrumb navigation | `generateBreadcrumbJsonLd` |
62
+ | App / tool promo section | `generateSoftwareApplicationJsonLd` |
63
+ | Plain marketing pages | `generateWebPageJsonLd` |
64
+ | Logo walls, testimonials, generic heroes | none — don't force structured data |
65
+
66
+ ## Rules
67
+
68
+ - Emit from **server components** so the script tags are in the initial HTML.
69
+ - One `<JsonLd />` per entity — map each job/event to its own tag instead of
70
+ merging unrelated entities into one block.
71
+ - Only emit when the schema's required fields exist (e.g. `JobPosting` needs
72
+ title, description, datePosted). Skip the tag entirely rather than emitting
73
+ partial data.
74
+ - Sanity data: run values through `stegaClean` from `next-sanity` before they
75
+ reach JSON-LD (see `components/layout/json-ld` for the pattern), otherwise
76
+ invisible stega characters leak into the emitted markup.
77
+ - Don't duplicate: if a page-level Sanity block already covers the content,
78
+ don't also emit it from the component.
79
+ - Generators absolutize app-relative URLs (`/jobs/123`) automatically; pass
80
+ absolute URLs (including Sanity CDN assets) as-is.
81
+
82
+ ## Verify
83
+
84
+ Check `view-source:` for each `application/ld+json` script and validate with
85
+ https://validator.schema.org or https://search.google.com/test/rich-results.
@@ -0,0 +1,67 @@
1
+ ---
2
+ name: manage-env
3
+ description: >-
4
+ Use when adding or changing an environment variable in a project that uses our
5
+ dotenvx setup. Decides secret vs trivial config: trivial/`NEXT_PUBLIC_*` the
6
+ agent handles fully, but for a secret the agent only wires the key in code
7
+ (process.env, validation schema, types) and hands the value-setting to the
8
+ human — it never types a secret value into a command. Triggers on "add an env
9
+ var", "new environment variable", "set an API key/token/secret", "put X in
10
+ .env", "encrypt env", or editing any `.env*` file.
11
+ ---
12
+
13
+ # Add or change an environment variable
14
+
15
+ This project uses **dotenvx**. Env files:
16
+
17
+ - `.env` — shared / development, committed **encrypted**
18
+ - `.env.preview`, `.env.production` — per-environment overrides, committed encrypted (created on demand)
19
+ - `.env.local` — plaintext, gitignored (personal local overrides)
20
+ - `.env.keys` — private keys, gitignored (backed up in the team password manager)
21
+
22
+ Scripts run `dotenvx run -f .env.$VERCEL_ENV -f .env -- …`, so the env-specific
23
+ file overrides `.env`. A **gitleaks** pre-commit hook + CI scan are the real
24
+ enforcement — this skill just makes the right choice the default.
25
+
26
+ ## 1. Secret or trivial config?
27
+
28
+ - **Secret** — API keys, tokens, DB URLs, anything you'd rotate if it leaked.
29
+ - **Trivial** — public base URLs, feature flags, anything `NEXT_PUBLIC_*` (it
30
+ ships to the browser anyway). Plaintext is fine; encrypting buys nothing.
31
+
32
+ ## 2a. Secret → never put the value in a command
33
+
34
+ A raw secret in a command (`env:set KEY <value>`) leaks the plaintext into the
35
+ tool call and the conversation context. So split the work:
36
+
37
+ - **Agent — code wiring only, never the value.** Read it via `process.env.KEY`,
38
+ add it to the env validation schema / types if the project has one, and add a
39
+ placeholder line to `.env.example`. Document the key name and where it's used.
40
+ - **Human — owns the value.** Either run `bun run env:set KEY <value>` yourself
41
+ (`--preview` / `--prod` for per-environment), or set it directly in the Vercel
42
+ environment. `env:set` encrypts in place and adds the matching
43
+ `DOTENV_PRIVATE_KEY*` to `.env.keys`.
44
+ - **Verify (agent):** only via `git diff` showing `KEY=encrypted:…`. Never run
45
+ `env:view` on a secret — it prints the decrypted value into context.
46
+ - Each `DOTENV_PRIVATE_KEY*` must exist on the matching Vercel environment and as a
47
+ CI secret, or it won't decrypt there.
48
+ - **Edge runtime caveat.** dotenvx only decrypts in the Node runtime (via
49
+ `instrumentation.ts`). A secret read in `middleware.ts` or a `runtime = "edge"`
50
+ route is **not** decrypted from `.env` — it must be set directly as a Vercel env
51
+ var. The encrypted `.env` covers Node functions + build, not the edge.
52
+
53
+ ## 2b. Trivial config → plaintext
54
+
55
+ Add it plaintext — no encryption — with `bun run env:set KEY value --plain`, or
56
+ just append a line to the file:
57
+
58
+ ```bash
59
+ NEXT_PUBLIC_FOO=bar
60
+ ```
61
+
62
+ gitleaks ignores non-secret plaintext, so it won't fail the scan.
63
+
64
+ ## Rule
65
+
66
+ Never commit a real secret in plaintext. If one lands there, bulk-encrypt it in
67
+ place with `bun run env:encrypt-all` (no value typed).
@@ -0,0 +1,22 @@
1
+ // Reads engines.node from cwd's package.json so one file works in this repo and
2
+ // every project it's injected into.
3
+ import { readFileSync } from "node:fs";
4
+
5
+ let required = Number.NaN;
6
+ try {
7
+ const { engines } = JSON.parse(readFileSync("package.json", "utf8"));
8
+ required = Number(engines?.node?.match(/\d+/)?.[0]);
9
+ } catch {
10
+ // Can't read or parse package.json — nothing to enforce, let install proceed.
11
+ process.exit(0);
12
+ }
13
+
14
+ if (
15
+ !Number.isNaN(required) &&
16
+ Number(process.versions.node.split(".")[0]) < required
17
+ ) {
18
+ console.error(
19
+ `\nNode ${required}+ required. You have ${process.versions.node}.\n`,
20
+ );
21
+ process.exit(1);
22
+ }
@@ -6,6 +6,9 @@ import {
6
6
  ALL_ARTICLES_QUERY,
7
7
  ARTICLE_QUERY,
8
8
  } from "@/lib/integrations/sanity/queries";
9
+ import { urlForImage } from "@/lib/integrations/sanity/utils/image";
10
+ import { buildPageJsonLd } from "@/lib/integrations/sanity/utils/page-json-ld";
11
+ import { generateArticleJsonLd, JsonLd } from "@/lib/utils/json-ld";
9
12
 
10
13
  export async function generateStaticParams() {
11
14
  if (!client) return [];
@@ -30,28 +33,52 @@ export default async function BlogArticlePage({ params }: Props) {
30
33
 
31
34
  if (!article) return notFound();
32
35
 
36
+ const articleUrl = `/blog/${slug}`;
37
+ const articleImage = article.featuredImage
38
+ ? urlForImage(article.featuredImage).url()
39
+ : article.metadata?.image
40
+ ? urlForImage(article.metadata.image).url()
41
+ : undefined;
42
+ const pageJsonLd = buildPageJsonLd(article.metadata?.jsonLd);
43
+
33
44
  return (
34
- <article className="container mx-auto px-4 py-8 max-w-4xl">
35
- <header className="mb-8">
36
- <h1 className="text-4xl font-bold mb-4">{article.title}</h1>
37
- {article.excerpt && (
38
- <p className="text-lg text-gray-600">{article.excerpt}</p>
39
- )}
40
- <div className="flex gap-4 mt-4 text-sm text-gray-500">
41
- {article.author && <span>By {article.author}</span>}
42
- {article.publishedAt && (
43
- <time dateTime={article.publishedAt}>
44
- {new Date(article.publishedAt).toLocaleDateString()}
45
- </time>
45
+ <>
46
+ <JsonLd
47
+ data={generateArticleJsonLd({
48
+ title: article.metadata?.title || article.title || "",
49
+ url: articleUrl,
50
+ description: article.metadata?.description || article.excerpt,
51
+ image: articleImage,
52
+ datePublished: article.publishedAt,
53
+ dateModified: article._updatedAt,
54
+ authorName: article.author,
55
+ })}
56
+ />
57
+ {pageJsonLd.map(({ key, data }) => (
58
+ <JsonLd key={key} data={data} />
59
+ ))}
60
+ <article className="container mx-auto px-4 py-8 max-w-4xl">
61
+ <header className="mb-8">
62
+ <h1 className="text-4xl font-bold mb-4">{article.title}</h1>
63
+ {article.excerpt && (
64
+ <p className="text-lg text-gray-600">{article.excerpt}</p>
46
65
  )}
47
- </div>
48
- </header>
49
-
50
- {article.content && (
51
- <div className="prose max-w-none">
52
- <RichText content={article.content} />
53
- </div>
54
- )}
55
- </article>
66
+ <div className="flex gap-4 mt-4 text-sm text-gray-500">
67
+ {article.author && <span>By {article.author}</span>}
68
+ {article.publishedAt && (
69
+ <time dateTime={article.publishedAt}>
70
+ {new Date(article.publishedAt).toLocaleDateString()}
71
+ </time>
72
+ )}
73
+ </div>
74
+ </header>
75
+
76
+ {article.content && (
77
+ <div className="prose max-w-none">
78
+ <RichText content={article.content} />
79
+ </div>
80
+ )}
81
+ </article>
82
+ </>
56
83
  );
57
84
  }
@@ -0,0 +1,54 @@
1
+ import Link from "next/link";
2
+ import { sanityFetch } from "@/lib/integrations/sanity/live";
3
+ import { ALL_ARTICLES_QUERY } from "@/lib/integrations/sanity/queries";
4
+
5
+ export const metadata = {
6
+ title: "Blog",
7
+ alternates: {
8
+ types: { "application/rss+xml": "/feed.xml" },
9
+ },
10
+ };
11
+
12
+ export default async function BlogIndexPage() {
13
+ const { data: articles } = await sanityFetch({ query: ALL_ARTICLES_QUERY });
14
+
15
+ return (
16
+ <main className="container mx-auto px-4 py-8 max-w-4xl">
17
+ <h1 className="text-4xl font-bold mb-8">Blog</h1>
18
+
19
+ {articles && articles.length > 0 ? (
20
+ <ul className="flex flex-col gap-8">
21
+ {articles.map((article) => {
22
+ const slug = article.slug?.current;
23
+ if (!slug) return null;
24
+
25
+ return (
26
+ <li key={article._id}>
27
+ <article>
28
+ <h2 className="text-2xl font-semibold mb-2">
29
+ <Link href={`/blog/${slug}`} className="hover:underline">
30
+ {article.title}
31
+ </Link>
32
+ </h2>
33
+ {article.excerpt && (
34
+ <p className="text-gray-600 mb-2">{article.excerpt}</p>
35
+ )}
36
+ <div className="flex gap-4 text-sm text-gray-500">
37
+ {article.author && <span>By {article.author}</span>}
38
+ {article.publishedAt && (
39
+ <time dateTime={article.publishedAt}>
40
+ {new Date(article.publishedAt).toLocaleDateString()}
41
+ </time>
42
+ )}
43
+ </div>
44
+ </article>
45
+ </li>
46
+ );
47
+ })}
48
+ </ul>
49
+ ) : (
50
+ <p className="text-gray-600">No articles published yet.</p>
51
+ )}
52
+ </main>
53
+ );
54
+ }
@@ -0,0 +1,36 @@
1
+ import { client } from "@/lib/integrations/sanity/client";
2
+ import { ALL_ARTICLES_QUERY } from "@/lib/integrations/sanity/queries";
3
+ import { generateRssFeed, rssErrorResponse } from "@/lib/utils/rss";
4
+ import { getBaseUrl } from "@/lib/utils/url";
5
+
6
+ export const dynamic = "force-dynamic";
7
+
8
+ export async function GET() {
9
+ const baseUrl = getBaseUrl();
10
+ if (!client) return rssErrorResponse();
11
+
12
+ try {
13
+ const articles = await client.fetch(ALL_ARTICLES_QUERY);
14
+
15
+ const items = (articles ?? [])
16
+ .filter((article) => article.slug?.current && !article.metadata?.noIndex)
17
+ .map((article) => ({
18
+ title: article.title ?? "Untitled",
19
+ link: `${baseUrl}/blog/${article.slug?.current}`,
20
+ description: article.excerpt ?? undefined,
21
+ pubDate: article.publishedAt ?? undefined,
22
+ authors: article.author ? [article.author] : undefined,
23
+ }));
24
+
25
+ return generateRssFeed({
26
+ title: `${new URL(baseUrl).hostname} Blog`,
27
+ link: `${baseUrl}/blog`,
28
+ description: "Latest articles",
29
+ feedUrl: `${baseUrl}/feed.xml`,
30
+ items,
31
+ });
32
+ } catch (error) {
33
+ console.error("Error generating RSS feed:", error);
34
+ return rssErrorResponse();
35
+ }
36
+ }
@@ -0,0 +1,83 @@
1
+ import { stegaClean } from "next-sanity";
2
+ import { sanityFetch } from "@/lib/integrations/sanity/live";
3
+ import { STRUCTURED_DATA_QUERY } from "@/lib/integrations/sanity/queries";
4
+ import {
5
+ generateOrganizationJsonLd,
6
+ generateWebSiteJsonLd,
7
+ JsonLd,
8
+ } from "@/lib/utils/json-ld";
9
+
10
+ type StructuredDataResult = {
11
+ name?: string | null;
12
+ legalName?: string | null;
13
+ description?: string | null;
14
+ url?: string | null;
15
+ logo?: string | null;
16
+ sameAs?: (string | null)[] | null;
17
+ contactPoint?: {
18
+ email?: string | null;
19
+ telephone?: string | null;
20
+ contactType?: string | null;
21
+ } | null;
22
+ address?: {
23
+ streetAddress?: string | null;
24
+ locality?: string | null;
25
+ region?: string | null;
26
+ postalCode?: string | null;
27
+ country?: string | null;
28
+ } | null;
29
+ } | null;
30
+
31
+ const nonEmpty = (value: unknown): string | undefined => {
32
+ const cleaned = stegaClean(value);
33
+ return typeof cleaned === "string" && cleaned.trim() !== ""
34
+ ? cleaned
35
+ : undefined;
36
+ };
37
+
38
+ /**
39
+ * Site-wide Organization + WebSite JSON-LD from the Structured Data
40
+ * singleton, with the static app values as fallback.
41
+ */
42
+ export async function SanityStructuredData({
43
+ fallbackName,
44
+ fallbackDescription,
45
+ fallbackLogo,
46
+ }: {
47
+ fallbackName: string;
48
+ fallbackDescription?: string;
49
+ fallbackLogo?: string;
50
+ }) {
51
+ const { data } = await sanityFetch({ query: STRUCTURED_DATA_QUERY });
52
+ // Deep-clean stega artifacts up front so nested objects (contactPoint,
53
+ // address) never leak visual-editing markers into the emitted JSON-LD.
54
+ const structuredData = stegaClean(data as unknown as StructuredDataResult);
55
+
56
+ const name = nonEmpty(structuredData?.name) ?? fallbackName;
57
+ const description =
58
+ nonEmpty(structuredData?.description) ?? fallbackDescription;
59
+ const url = nonEmpty(structuredData?.url);
60
+ const logo = nonEmpty(structuredData?.logo) ?? fallbackLogo;
61
+
62
+ const sameAs = (structuredData?.sameAs ?? [])
63
+ .map(nonEmpty)
64
+ .filter((value): value is string => Boolean(value));
65
+
66
+ return (
67
+ <>
68
+ <JsonLd data={generateWebSiteJsonLd({ name, url, description })} />
69
+ <JsonLd
70
+ data={generateOrganizationJsonLd({
71
+ name,
72
+ url,
73
+ logo,
74
+ description,
75
+ legalName: nonEmpty(structuredData?.legalName),
76
+ sameAs: sameAs.length ? sameAs : undefined,
77
+ contactPoint: structuredData?.contactPoint,
78
+ address: structuredData?.address,
79
+ })}
80
+ />
81
+ </>
82
+ );
83
+ }