bsmnt 0.0.2 → 0.1.1

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 (346) hide show
  1. package/.github/workflows/release.yml +2 -0
  2. package/CHANGELOG.md +31 -0
  3. package/CLAUDE.md +42 -24
  4. package/README.md +33 -12
  5. package/biome.json +1 -0
  6. package/bun.lock +2 -1
  7. package/docs/architecture.drawio +250 -0
  8. package/docs/architecture.mermaid +85 -0
  9. package/package.json +42 -42
  10. package/{bin → packages/cli/bin}/index.js +28 -31
  11. package/packages/cli/package.json +16 -0
  12. package/{src → packages/cli/src}/commands/add-integration.js +22 -37
  13. package/{src → packages/cli/src}/commands/create.js +125 -131
  14. package/packages/create-basement-app/integrations/sanity/config.js +46 -0
  15. package/{src → packages/create-basement-app/integrations/sanity}/mergers/check-integration-merger.js +1 -1
  16. package/{src → packages/create-basement-app/integrations/sanity}/mergers/layout-merger.js +1 -1
  17. package/{src → packages/create-basement-app/integrations/sanity}/mergers/sitemap-merger.js +1 -1
  18. package/packages/create-basement-app/package.json +10 -0
  19. package/packages/create-basement-app/src/configs/animations.js +28 -0
  20. package/packages/create-basement-app/src/index.js +15 -0
  21. package/packages/create-basement-app/src/mergers/check-integration-merger.js +105 -0
  22. package/{src → packages/create-basement-app/src}/mergers/config.js +5 -33
  23. package/{src → packages/create-basement-app/src}/mergers/index.js +89 -98
  24. package/packages/create-basement-app/src/mergers/layout-merger.js +223 -0
  25. package/packages/create-basement-app/src/mergers/sitemap-merger.js +121 -0
  26. package/packages/create-basement-app/template-hooks/config.js +38 -0
  27. package/packages/create-basement-app/templates/default/.biome/plugins/README.md +21 -0
  28. package/packages/create-basement-app/templates/default/.biome/plugins/no-anchor-element.grit +12 -0
  29. package/packages/create-basement-app/templates/default/.biome/plugins/no-relative-parent-imports.grit +10 -0
  30. package/packages/create-basement-app/templates/default/.biome/plugins/no-unnecessary-forwardref.grit +9 -0
  31. package/packages/create-basement-app/templates/default/.cursor/rules/README.md +184 -0
  32. package/packages/create-basement-app/templates/default/.cursor/rules/architecture.mdc +437 -0
  33. package/packages/create-basement-app/templates/default/.cursor/rules/components.mdc +436 -0
  34. package/packages/create-basement-app/templates/default/.cursor/rules/integrations.mdc +447 -0
  35. package/packages/create-basement-app/templates/default/.cursor/rules/main.mdc +278 -0
  36. package/packages/create-basement-app/templates/default/.cursor/rules/styling.mdc +433 -0
  37. package/packages/create-basement-app/templates/default/.editorconfig +40 -0
  38. package/packages/create-basement-app/templates/default/.env.example +81 -0
  39. package/packages/create-basement-app/templates/default/.gitattributes +19 -0
  40. package/packages/create-basement-app/templates/default/.github/PULL_REQUEST_TEMPLATE.md +14 -0
  41. package/packages/create-basement-app/templates/default/.github/workflows/lighthouse-to-slack.yml +136 -0
  42. package/packages/create-basement-app/templates/default/.vscode/extensions.json +20 -0
  43. package/packages/create-basement-app/templates/default/.vscode/settings.json +105 -0
  44. package/packages/create-basement-app/templates/default/README.md +221 -0
  45. package/packages/create-basement-app/templates/default/_gitignore +67 -0
  46. package/packages/create-basement-app/templates/default/app/favicon.ico +0 -0
  47. package/packages/create-basement-app/templates/default/app/layout.tsx +104 -0
  48. package/packages/create-basement-app/templates/default/app/page.tsx +275 -0
  49. package/packages/create-basement-app/templates/default/app/robots.ts +15 -0
  50. package/packages/create-basement-app/templates/default/app/sitemap.ts +16 -0
  51. package/packages/create-basement-app/templates/default/biome.json +250 -0
  52. package/packages/create-basement-app/templates/default/components/basement.svg +1 -0
  53. package/packages/create-basement-app/templates/default/components/layout/footer/index.tsx +27 -0
  54. package/packages/create-basement-app/templates/default/components/layout/header/index.tsx +11 -0
  55. package/packages/create-basement-app/templates/default/components/layout/theme/index.tsx +66 -0
  56. package/packages/create-basement-app/templates/default/components/layout/wrapper/index.tsx +65 -0
  57. package/packages/create-basement-app/templates/default/components/ui/README.md +77 -0
  58. package/packages/create-basement-app/templates/default/components/ui/image/README.md +37 -0
  59. package/packages/create-basement-app/templates/default/components/ui/image/index.tsx +224 -0
  60. package/packages/create-basement-app/templates/default/components/ui/link/index.tsx +146 -0
  61. package/packages/create-basement-app/templates/default/lib/README.md +33 -0
  62. package/packages/create-basement-app/templates/default/lib/hooks/index.ts +12 -0
  63. package/packages/create-basement-app/templates/default/lib/hooks/use-device-detection.ts +81 -0
  64. package/packages/create-basement-app/templates/default/lib/hooks/use-media-breakpoint.ts +15 -0
  65. package/packages/create-basement-app/templates/default/lib/hooks/use-prefetch.ts +74 -0
  66. package/packages/create-basement-app/templates/default/lib/scripts/dev.ts +52 -0
  67. package/packages/create-basement-app/templates/default/lib/scripts/generate-component.ts +322 -0
  68. package/packages/create-basement-app/templates/default/lib/scripts/generate-page.ts +193 -0
  69. package/packages/create-basement-app/templates/default/lib/scripts/generate.ts +79 -0
  70. package/packages/create-basement-app/templates/default/lib/scripts/utils.ts +246 -0
  71. package/packages/create-basement-app/templates/default/lib/store/app.ts +11 -0
  72. package/packages/create-basement-app/templates/default/lib/store/index.ts +11 -0
  73. package/packages/create-basement-app/templates/default/lib/styles/README.md +64 -0
  74. package/packages/create-basement-app/templates/default/lib/styles/cn.ts +7 -0
  75. package/packages/create-basement-app/templates/default/lib/styles/colors.ts +63 -0
  76. package/packages/create-basement-app/templates/default/lib/styles/config.ts +34 -0
  77. package/packages/create-basement-app/templates/default/lib/styles/css/global.css +85 -0
  78. package/packages/create-basement-app/templates/default/lib/styles/css/index.css +6 -0
  79. package/packages/create-basement-app/templates/default/lib/styles/css/reset.css +166 -0
  80. package/packages/create-basement-app/templates/default/lib/styles/css/root.css +68 -0
  81. package/packages/create-basement-app/templates/default/lib/styles/css/tailwind.css +132 -0
  82. package/packages/create-basement-app/templates/default/lib/styles/easings.ts +21 -0
  83. package/packages/create-basement-app/templates/default/lib/styles/fonts.ts +28 -0
  84. package/packages/create-basement-app/templates/default/lib/styles/index.ts +12 -0
  85. package/packages/create-basement-app/templates/default/lib/styles/layout.mjs +27 -0
  86. package/packages/create-basement-app/templates/default/lib/styles/scripts/README.md +29 -0
  87. package/packages/create-basement-app/templates/default/lib/styles/scripts/generate-root.ts +57 -0
  88. package/packages/create-basement-app/templates/default/lib/styles/scripts/generate-tailwind.ts +162 -0
  89. package/packages/create-basement-app/templates/default/lib/styles/scripts/postcss-functions.mjs +168 -0
  90. package/packages/create-basement-app/templates/default/lib/styles/scripts/setup-styles.ts +24 -0
  91. package/packages/create-basement-app/templates/default/lib/styles/scripts/utils.ts +20 -0
  92. package/packages/create-basement-app/templates/default/lib/styles/typography.ts +36 -0
  93. package/packages/create-basement-app/templates/default/lib/utils/README.md +40 -0
  94. package/packages/create-basement-app/templates/default/lib/utils/css.d.ts +21 -0
  95. package/packages/create-basement-app/templates/default/lib/utils/easings.ts +240 -0
  96. package/packages/create-basement-app/templates/default/lib/utils/fetch.ts +84 -0
  97. package/packages/create-basement-app/templates/default/lib/utils/math.test.ts +221 -0
  98. package/packages/create-basement-app/templates/default/lib/utils/math.ts +236 -0
  99. package/packages/create-basement-app/templates/default/lib/utils/metadata.ts +126 -0
  100. package/packages/create-basement-app/templates/default/lib/utils/strings.test.ts +166 -0
  101. package/packages/create-basement-app/templates/default/lib/utils/strings.ts +246 -0
  102. package/packages/create-basement-app/templates/default/lib/utils/types.d.ts +15 -0
  103. package/packages/create-basement-app/templates/default/lib/utils/viewport.test.ts +256 -0
  104. package/packages/create-basement-app/templates/default/lib/utils/viewport.ts +193 -0
  105. package/packages/create-basement-app/templates/default/next.config.ts +142 -0
  106. package/packages/create-basement-app/templates/default/package.json +62 -0
  107. package/packages/create-basement-app/templates/default/postcss.config.mjs +42 -0
  108. package/packages/create-basement-app/templates/default/public/fonts/geist/Geist-Mono.woff2 +0 -0
  109. package/packages/create-basement-app/templates/default/tsconfig.json +43 -0
  110. package/packages/create-basement-app/templates/experiment/.biome/plugins/README.md +21 -0
  111. package/packages/create-basement-app/templates/experiment/.biome/plugins/no-anchor-element.grit +12 -0
  112. package/packages/create-basement-app/templates/experiment/.biome/plugins/no-relative-parent-imports.grit +10 -0
  113. package/packages/create-basement-app/templates/experiment/.biome/plugins/no-unnecessary-forwardref.grit +9 -0
  114. package/packages/create-basement-app/templates/experiment/.cursor/rules/README.md +184 -0
  115. package/packages/create-basement-app/templates/experiment/.cursor/rules/architecture.mdc +437 -0
  116. package/packages/create-basement-app/templates/experiment/.cursor/rules/components.mdc +436 -0
  117. package/packages/create-basement-app/templates/experiment/.cursor/rules/integrations.mdc +447 -0
  118. package/packages/create-basement-app/templates/experiment/.cursor/rules/main.mdc +278 -0
  119. package/packages/create-basement-app/templates/experiment/.cursor/rules/styling.mdc +433 -0
  120. package/packages/create-basement-app/templates/experiment/.editorconfig +40 -0
  121. package/packages/create-basement-app/templates/experiment/.env.example +81 -0
  122. package/packages/create-basement-app/templates/experiment/.gitattributes +19 -0
  123. package/packages/create-basement-app/templates/experiment/.github/PULL_REQUEST_TEMPLATE.md +14 -0
  124. package/packages/create-basement-app/templates/experiment/.github/workflows/lighthouse-to-slack.yml +136 -0
  125. package/packages/create-basement-app/templates/experiment/.vscode/extensions.json +20 -0
  126. package/packages/create-basement-app/templates/experiment/.vscode/settings.json +105 -0
  127. package/packages/create-basement-app/templates/experiment/README.md +221 -0
  128. package/packages/create-basement-app/templates/experiment/_gitignore +67 -0
  129. package/packages/create-basement-app/templates/experiment/app/favicon.ico +0 -0
  130. package/packages/create-basement-app/templates/experiment/app/layout.tsx +104 -0
  131. package/packages/create-basement-app/templates/experiment/app/page.tsx +275 -0
  132. package/packages/create-basement-app/templates/experiment/app/robots.ts +15 -0
  133. package/packages/create-basement-app/templates/experiment/app/sitemap.ts +16 -0
  134. package/packages/create-basement-app/templates/experiment/biome.json +250 -0
  135. package/packages/create-basement-app/templates/experiment/components/basement.svg +1 -0
  136. package/packages/create-basement-app/templates/experiment/components/layout/footer/index.tsx +27 -0
  137. package/packages/create-basement-app/templates/experiment/components/layout/header/index.tsx +58 -0
  138. package/packages/create-basement-app/templates/experiment/components/layout/navigation-menu.tsx +127 -0
  139. package/packages/create-basement-app/templates/experiment/components/layout/theme/index.tsx +66 -0
  140. package/packages/create-basement-app/templates/experiment/components/layout/wrapper/index.tsx +65 -0
  141. package/packages/create-basement-app/templates/experiment/components/ui/README.md +77 -0
  142. package/packages/create-basement-app/templates/experiment/components/ui/image/README.md +37 -0
  143. package/packages/create-basement-app/templates/experiment/components/ui/image/index.tsx +224 -0
  144. package/packages/create-basement-app/templates/experiment/components/ui/link/index.tsx +146 -0
  145. package/packages/create-basement-app/templates/experiment/lib/README.md +33 -0
  146. package/packages/create-basement-app/templates/experiment/lib/constants.ts +12 -0
  147. package/packages/create-basement-app/templates/experiment/lib/hooks/index.ts +12 -0
  148. package/packages/create-basement-app/templates/experiment/lib/hooks/use-device-detection.ts +81 -0
  149. package/packages/create-basement-app/templates/experiment/lib/hooks/use-media-breakpoint.ts +15 -0
  150. package/packages/create-basement-app/templates/experiment/lib/hooks/use-prefetch.ts +74 -0
  151. package/packages/create-basement-app/templates/experiment/lib/integrations/.gitkeep +0 -0
  152. package/packages/create-basement-app/templates/experiment/lib/scripts/dev.ts +52 -0
  153. package/packages/create-basement-app/templates/experiment/lib/scripts/generate-component.ts +322 -0
  154. package/packages/create-basement-app/templates/experiment/lib/scripts/generate-page.ts +193 -0
  155. package/packages/create-basement-app/templates/experiment/lib/scripts/generate.ts +79 -0
  156. package/packages/create-basement-app/templates/experiment/lib/scripts/utils.ts +246 -0
  157. package/packages/create-basement-app/templates/experiment/lib/store/app.ts +11 -0
  158. package/packages/create-basement-app/templates/experiment/lib/store/index.ts +11 -0
  159. package/packages/create-basement-app/templates/experiment/lib/styles/README.md +64 -0
  160. package/packages/create-basement-app/templates/experiment/lib/styles/cn.ts +7 -0
  161. package/packages/create-basement-app/templates/experiment/lib/styles/colors.ts +63 -0
  162. package/packages/create-basement-app/templates/experiment/lib/styles/config.ts +34 -0
  163. package/packages/create-basement-app/templates/experiment/lib/styles/css/global.css +85 -0
  164. package/packages/create-basement-app/templates/experiment/lib/styles/css/index.css +6 -0
  165. package/packages/create-basement-app/templates/experiment/lib/styles/css/reset.css +166 -0
  166. package/packages/create-basement-app/templates/experiment/lib/styles/css/root.css +68 -0
  167. package/packages/create-basement-app/templates/experiment/lib/styles/css/tailwind.css +132 -0
  168. package/packages/create-basement-app/templates/experiment/lib/styles/easings.ts +21 -0
  169. package/packages/create-basement-app/templates/experiment/lib/styles/fonts.ts +28 -0
  170. package/packages/create-basement-app/templates/experiment/lib/styles/index.ts +12 -0
  171. package/packages/create-basement-app/templates/experiment/lib/styles/layout.mjs +27 -0
  172. package/packages/create-basement-app/templates/experiment/lib/styles/scripts/README.md +29 -0
  173. package/packages/create-basement-app/templates/experiment/lib/styles/scripts/generate-root.ts +57 -0
  174. package/packages/create-basement-app/templates/experiment/lib/styles/scripts/generate-tailwind.ts +162 -0
  175. package/packages/create-basement-app/templates/experiment/lib/styles/scripts/postcss-functions.mjs +168 -0
  176. package/packages/create-basement-app/templates/experiment/lib/styles/scripts/setup-styles.ts +24 -0
  177. package/packages/create-basement-app/templates/experiment/lib/styles/scripts/utils.ts +20 -0
  178. package/packages/create-basement-app/templates/experiment/lib/styles/typography.ts +36 -0
  179. package/packages/create-basement-app/templates/experiment/lib/utils/README.md +40 -0
  180. package/packages/create-basement-app/templates/experiment/lib/utils/css.d.ts +21 -0
  181. package/packages/create-basement-app/templates/experiment/lib/utils/easings.ts +240 -0
  182. package/packages/create-basement-app/templates/experiment/lib/utils/fetch.ts +84 -0
  183. package/packages/create-basement-app/templates/experiment/lib/utils/math.test.ts +221 -0
  184. package/packages/create-basement-app/templates/experiment/lib/utils/math.ts +236 -0
  185. package/packages/create-basement-app/templates/experiment/lib/utils/metadata.ts +126 -0
  186. package/packages/create-basement-app/templates/experiment/lib/utils/strings.test.ts +166 -0
  187. package/packages/create-basement-app/templates/experiment/lib/utils/strings.ts +246 -0
  188. package/packages/create-basement-app/templates/experiment/lib/utils/types.d.ts +15 -0
  189. package/packages/create-basement-app/templates/experiment/lib/utils/viewport.test.ts +256 -0
  190. package/packages/create-basement-app/templates/experiment/lib/utils/viewport.ts +193 -0
  191. package/packages/create-basement-app/templates/experiment/next.config.ts +142 -0
  192. package/packages/create-basement-app/templates/experiment/package.json +69 -0
  193. package/packages/create-basement-app/templates/experiment/postcss.config.mjs +42 -0
  194. package/packages/create-basement-app/templates/experiment/public/fonts/geist/Geist-Mono.woff2 +0 -0
  195. package/packages/create-basement-app/templates/experiment/tsconfig.json +43 -0
  196. package/packages/create-basement-app/templates/webgl/.biome/plugins/README.md +21 -0
  197. package/packages/create-basement-app/templates/webgl/.biome/plugins/no-anchor-element.grit +12 -0
  198. package/packages/create-basement-app/templates/webgl/.biome/plugins/no-relative-parent-imports.grit +10 -0
  199. package/packages/create-basement-app/templates/webgl/.biome/plugins/no-unnecessary-forwardref.grit +9 -0
  200. package/packages/create-basement-app/templates/webgl/.cursor/rules/README.md +184 -0
  201. package/packages/create-basement-app/templates/webgl/.cursor/rules/architecture.mdc +437 -0
  202. package/packages/create-basement-app/templates/webgl/.cursor/rules/components.mdc +436 -0
  203. package/packages/create-basement-app/templates/webgl/.cursor/rules/integrations.mdc +447 -0
  204. package/packages/create-basement-app/templates/webgl/.cursor/rules/main.mdc +278 -0
  205. package/packages/create-basement-app/templates/webgl/.cursor/rules/styling.mdc +433 -0
  206. package/packages/create-basement-app/templates/webgl/.editorconfig +40 -0
  207. package/packages/create-basement-app/templates/webgl/.env.example +81 -0
  208. package/packages/create-basement-app/templates/webgl/.gitattributes +19 -0
  209. package/packages/create-basement-app/templates/webgl/.github/PULL_REQUEST_TEMPLATE.md +14 -0
  210. package/packages/create-basement-app/templates/webgl/.github/workflows/lighthouse-to-slack.yml +136 -0
  211. package/packages/create-basement-app/templates/webgl/.vscode/extensions.json +20 -0
  212. package/packages/create-basement-app/templates/webgl/.vscode/settings.json +105 -0
  213. package/packages/create-basement-app/templates/webgl/README.md +221 -0
  214. package/packages/create-basement-app/templates/webgl/_gitignore +67 -0
  215. package/packages/create-basement-app/templates/webgl/app/favicon.ico +0 -0
  216. package/packages/create-basement-app/templates/webgl/app/layout.tsx +104 -0
  217. package/packages/create-basement-app/templates/webgl/app/page.tsx +10 -0
  218. package/packages/create-basement-app/templates/webgl/app/robots.ts +15 -0
  219. package/packages/create-basement-app/templates/webgl/app/sitemap.ts +16 -0
  220. package/packages/create-basement-app/templates/webgl/biome.json +250 -0
  221. package/packages/create-basement-app/templates/webgl/components/basement.svg +1 -0
  222. package/packages/create-basement-app/templates/webgl/components/layout/footer/index.tsx +27 -0
  223. package/packages/create-basement-app/templates/webgl/components/layout/header/index.tsx +11 -0
  224. package/packages/create-basement-app/templates/webgl/components/layout/theme/index.tsx +66 -0
  225. package/packages/create-basement-app/templates/webgl/components/layout/wrapper/index.tsx +65 -0
  226. package/packages/create-basement-app/templates/webgl/components/ui/README.md +77 -0
  227. package/packages/create-basement-app/templates/webgl/components/ui/image/README.md +37 -0
  228. package/packages/create-basement-app/templates/webgl/components/ui/image/index.tsx +224 -0
  229. package/packages/create-basement-app/templates/webgl/components/ui/link/index.tsx +146 -0
  230. package/packages/create-basement-app/templates/webgl/components/webgl/canvas/dynamic.tsx +34 -0
  231. package/packages/create-basement-app/templates/webgl/components/webgl/canvas/index.tsx +38 -0
  232. package/packages/create-basement-app/templates/webgl/components/webgl/components/scene/index.tsx +29 -0
  233. package/packages/create-basement-app/templates/webgl/lib/README.md +33 -0
  234. package/packages/create-basement-app/templates/webgl/lib/hooks/index.ts +12 -0
  235. package/packages/create-basement-app/templates/webgl/lib/hooks/use-device-detection.ts +81 -0
  236. package/packages/create-basement-app/templates/webgl/lib/hooks/use-media-breakpoint.ts +15 -0
  237. package/packages/create-basement-app/templates/webgl/lib/hooks/use-prefetch.ts +74 -0
  238. package/packages/create-basement-app/templates/webgl/lib/integrations/.gitkeep +0 -0
  239. package/packages/create-basement-app/templates/webgl/lib/renderer.ts +7 -0
  240. package/packages/create-basement-app/templates/webgl/lib/scripts/dev.ts +52 -0
  241. package/packages/create-basement-app/templates/webgl/lib/scripts/generate-component.ts +322 -0
  242. package/packages/create-basement-app/templates/webgl/lib/scripts/generate-page.ts +193 -0
  243. package/packages/create-basement-app/templates/webgl/lib/scripts/generate.ts +79 -0
  244. package/packages/create-basement-app/templates/webgl/lib/scripts/utils.ts +246 -0
  245. package/packages/create-basement-app/templates/webgl/lib/store/app.ts +11 -0
  246. package/packages/create-basement-app/templates/webgl/lib/store/index.ts +11 -0
  247. package/packages/create-basement-app/templates/webgl/lib/styles/README.md +64 -0
  248. package/packages/create-basement-app/templates/webgl/lib/styles/cn.ts +7 -0
  249. package/packages/create-basement-app/templates/webgl/lib/styles/colors.ts +63 -0
  250. package/packages/create-basement-app/templates/webgl/lib/styles/config.ts +34 -0
  251. package/packages/create-basement-app/templates/webgl/lib/styles/css/global.css +85 -0
  252. package/packages/create-basement-app/templates/webgl/lib/styles/css/index.css +6 -0
  253. package/packages/create-basement-app/templates/webgl/lib/styles/css/reset.css +166 -0
  254. package/packages/create-basement-app/templates/webgl/lib/styles/css/root.css +68 -0
  255. package/packages/create-basement-app/templates/webgl/lib/styles/css/tailwind.css +132 -0
  256. package/packages/create-basement-app/templates/webgl/lib/styles/easings.ts +21 -0
  257. package/packages/create-basement-app/templates/webgl/lib/styles/fonts.ts +28 -0
  258. package/packages/create-basement-app/templates/webgl/lib/styles/index.ts +12 -0
  259. package/packages/create-basement-app/templates/webgl/lib/styles/layout.mjs +27 -0
  260. package/packages/create-basement-app/templates/webgl/lib/styles/scripts/README.md +29 -0
  261. package/packages/create-basement-app/templates/webgl/lib/styles/scripts/generate-root.ts +57 -0
  262. package/packages/create-basement-app/templates/webgl/lib/styles/scripts/generate-tailwind.ts +162 -0
  263. package/packages/create-basement-app/templates/webgl/lib/styles/scripts/postcss-functions.mjs +168 -0
  264. package/packages/create-basement-app/templates/webgl/lib/styles/scripts/setup-styles.ts +24 -0
  265. package/packages/create-basement-app/templates/webgl/lib/styles/scripts/utils.ts +20 -0
  266. package/packages/create-basement-app/templates/webgl/lib/styles/typography.ts +36 -0
  267. package/packages/create-basement-app/templates/webgl/lib/utils/README.md +40 -0
  268. package/packages/create-basement-app/templates/webgl/lib/utils/css.d.ts +21 -0
  269. package/packages/create-basement-app/templates/webgl/lib/utils/easings.ts +240 -0
  270. package/packages/create-basement-app/templates/webgl/lib/utils/fetch.ts +84 -0
  271. package/packages/create-basement-app/templates/webgl/lib/utils/math.test.ts +221 -0
  272. package/packages/create-basement-app/templates/webgl/lib/utils/math.ts +236 -0
  273. package/packages/create-basement-app/templates/webgl/lib/utils/metadata.ts +126 -0
  274. package/packages/create-basement-app/templates/webgl/lib/utils/strings.test.ts +166 -0
  275. package/packages/create-basement-app/templates/webgl/lib/utils/strings.ts +246 -0
  276. package/packages/create-basement-app/templates/webgl/lib/utils/types.d.ts +15 -0
  277. package/packages/create-basement-app/templates/webgl/lib/utils/viewport.test.ts +256 -0
  278. package/packages/create-basement-app/templates/webgl/lib/utils/viewport.ts +193 -0
  279. package/packages/create-basement-app/templates/webgl/next.config.ts +142 -0
  280. package/packages/create-basement-app/templates/webgl/package.json +68 -0
  281. package/packages/create-basement-app/templates/webgl/postcss.config.mjs +42 -0
  282. package/packages/create-basement-app/templates/webgl/public/fonts/geist/Geist-Mono.woff2 +0 -0
  283. package/packages/create-basement-app/templates/webgl/tsconfig.json +43 -0
  284. package/tasks/.last-branch +1 -0
  285. package/tasks/CLAUDE.md +104 -0
  286. package/tasks/archive/2026-02-09-next-starter-dynamic-layers/prd.json +153 -0
  287. package/tasks/archive/2026-02-09-next-starter-dynamic-layers/progress.txt +115 -0
  288. package/tasks/prd-project-restructure.md +375 -0
  289. package/tasks/prd.json +227 -91
  290. package/tasks/progress.txt +281 -87
  291. package/tasks/ralph.sh +113 -0
  292. package/integrations/basehub/README.md +0 -3
  293. package/layers/experiment/components/layout/header/index.tsx +0 -58
  294. package/layers/experiment/components/layout/navigation-menu.tsx +0 -127
  295. package/layers/experiment/lib/constants.ts +0 -12
  296. package/layers/webgl/app/page.tsx +0 -10
  297. package/layers/webgl/components/webgl/canvas/dynamic.tsx +0 -34
  298. package/layers/webgl/components/webgl/canvas/index.tsx +0 -43
  299. package/layers/webgl/components/webgl/components/scene/index.tsx +0 -21
  300. package/src/mergers/next-config-merger.js +0 -63
  301. /package/{src → packages/cli/src}/commands/setup-sanity.js +0 -0
  302. /package/{src → packages/cli/src}/commands/worktree.js +0 -0
  303. /package/{integrations/sanity → packages/create-basement-app/integrations/sanity/files}/app/api/draft-mode/disable/route.ts +0 -0
  304. /package/{integrations/sanity → packages/create-basement-app/integrations/sanity/files}/app/api/draft-mode/enable/route.ts +0 -0
  305. /package/{integrations/sanity → packages/create-basement-app/integrations/sanity/files}/app/api/revalidate/route.ts +0 -0
  306. /package/{integrations/sanity → packages/create-basement-app/integrations/sanity/files}/app/layout.tsx +0 -0
  307. /package/{integrations/sanity → packages/create-basement-app/integrations/sanity/files}/app/sitemap.ts +0 -0
  308. /package/{integrations/sanity → packages/create-basement-app/integrations/sanity/files}/app/studio/[[...tool]]/page.tsx +0 -0
  309. /package/{integrations/sanity → packages/create-basement-app/integrations/sanity/files}/app/studio/layout.tsx +0 -0
  310. /package/{integrations/sanity → packages/create-basement-app/integrations/sanity/files}/components/ui/sanity-image/index.tsx +0 -0
  311. /package/{integrations/sanity → packages/create-basement-app/integrations/sanity/files}/lib/integrations/README.md +0 -0
  312. /package/{integrations/sanity → packages/create-basement-app/integrations/sanity/files}/lib/integrations/check-integration.ts +0 -0
  313. /package/{integrations/sanity → packages/create-basement-app/integrations/sanity/files}/lib/integrations/sanity/README.md +0 -0
  314. /package/{integrations/sanity → packages/create-basement-app/integrations/sanity/files}/lib/integrations/sanity/client.ts +0 -0
  315. /package/{integrations/sanity → packages/create-basement-app/integrations/sanity/files}/lib/integrations/sanity/components/disable-draft-mode.tsx +0 -0
  316. /package/{integrations/sanity → packages/create-basement-app/integrations/sanity/files}/lib/integrations/sanity/components/rich-text.tsx +0 -0
  317. /package/{integrations/sanity → packages/create-basement-app/integrations/sanity/files}/lib/integrations/sanity/env.ts +0 -0
  318. /package/{integrations/sanity → packages/create-basement-app/integrations/sanity/files}/lib/integrations/sanity/live/index.tsx +0 -0
  319. /package/{integrations/sanity → packages/create-basement-app/integrations/sanity/files}/lib/integrations/sanity/queries.ts +0 -0
  320. /package/{integrations/sanity → packages/create-basement-app/integrations/sanity/files}/lib/integrations/sanity/sanity.cli.ts +0 -0
  321. /package/{integrations/sanity → packages/create-basement-app/integrations/sanity/files}/lib/integrations/sanity/sanity.config.ts +0 -0
  322. /package/{integrations/sanity → packages/create-basement-app/integrations/sanity/files}/lib/integrations/sanity/sanity.types.ts +0 -0
  323. /package/{integrations/sanity → packages/create-basement-app/integrations/sanity/files}/lib/integrations/sanity/schema.json +0 -0
  324. /package/{integrations/sanity → packages/create-basement-app/integrations/sanity/files}/lib/integrations/sanity/schemas/article.ts +0 -0
  325. /package/{integrations/sanity → packages/create-basement-app/integrations/sanity/files}/lib/integrations/sanity/schemas/example.ts +0 -0
  326. /package/{integrations/sanity → packages/create-basement-app/integrations/sanity/files}/lib/integrations/sanity/schemas/index.ts +0 -0
  327. /package/{integrations/sanity → packages/create-basement-app/integrations/sanity/files}/lib/integrations/sanity/schemas/link.ts +0 -0
  328. /package/{integrations/sanity → packages/create-basement-app/integrations/sanity/files}/lib/integrations/sanity/schemas/metadata.ts +0 -0
  329. /package/{integrations/sanity → packages/create-basement-app/integrations/sanity/files}/lib/integrations/sanity/schemas/navigation.ts +0 -0
  330. /package/{integrations/sanity → packages/create-basement-app/integrations/sanity/files}/lib/integrations/sanity/schemas/page.ts +0 -0
  331. /package/{integrations/sanity → packages/create-basement-app/integrations/sanity/files}/lib/integrations/sanity/schemas/richText.ts +0 -0
  332. /package/{integrations/sanity → packages/create-basement-app/integrations/sanity/files}/lib/integrations/sanity/structure.ts +0 -0
  333. /package/{integrations/sanity → packages/create-basement-app/integrations/sanity/files}/lib/integrations/sanity/utils/image.ts +0 -0
  334. /package/{integrations/sanity → packages/create-basement-app/integrations/sanity/files}/lib/integrations/sanity/utils/link.ts +0 -0
  335. /package/{integrations/sanity → packages/create-basement-app/integrations/sanity/files}/lib/scripts/copy-sanity-mcp.ts +0 -0
  336. /package/{integrations/sanity → packages/create-basement-app/integrations/sanity/files}/lib/scripts/generate-page.ts +0 -0
  337. /package/{integrations/sanity → packages/create-basement-app/integrations/sanity/files}/lib/utils/metadata.ts +0 -0
  338. /package/{plugins → packages/create-basement-app/plugins}/README.md +0 -0
  339. /package/{plugins → packages/create-basement-app/plugins}/no-anchor-element.grit +0 -0
  340. /package/{plugins → packages/create-basement-app/plugins}/no-relative-parent-imports.grit +0 -0
  341. /package/{plugins → packages/create-basement-app/plugins}/no-unnecessary-forwardref.grit +0 -0
  342. /package/{template-hooks → packages/create-basement-app/template-hooks}/use-battery.ts +0 -0
  343. /package/{template-hooks → packages/create-basement-app/template-hooks}/use-device-perf.ts +0 -0
  344. /package/{template-hooks → packages/create-basement-app/template-hooks}/use-intersection-observer.ts +0 -0
  345. /package/{template-hooks → packages/create-basement-app/template-hooks}/use-media.ts +0 -0
  346. /package/{layers/webgpu → packages/create-basement-app/templates/default/lib/integrations}/.gitkeep +0 -0
@@ -1,10 +1,8 @@
1
- // src/mergers/index.js
1
+ // packages/create-basement-app/src/mergers/index.js
2
2
 
3
- import os from "node:os";
4
3
  import path from "node:path";
5
4
  import { fileURLToPath } from "node:url";
6
5
  import fs from "fs-extra";
7
- import tiged from "tiged";
8
6
  import { getCmsConfig, getLayerConfig } from "./config.js";
9
7
 
10
8
  const __filename = fileURLToPath(import.meta.url);
@@ -17,16 +15,17 @@ const __dirname = path.dirname(__filename);
17
15
  const CMS_MERGERS = {
18
16
  sanity: {
19
17
  "app/layout.tsx": () =>
20
- import("./layout-merger.js").then((m) => m.mergeLayout),
18
+ import("../../integrations/sanity/mergers/layout-merger.js").then(
19
+ (m) => m.mergeLayout,
20
+ ),
21
21
  "app/sitemap.ts": () =>
22
- import("./sitemap-merger.js").then((m) => m.mergeSitemap),
23
- "lib/integrations/check-integration.ts": () =>
24
- import("./check-integration-merger.js").then(
25
- (m) => m.mergeCheckIntegration,
22
+ import("../../integrations/sanity/mergers/sitemap-merger.js").then(
23
+ (m) => m.mergeSitemap,
26
24
  ),
27
- },
28
- basehub: {
29
- // BaseHub mergers would go here when implemented
25
+ "lib/integrations/check-integration.ts": () =>
26
+ import(
27
+ "../../integrations/sanity/mergers/check-integration-merger.js"
28
+ ).then((m) => m.mergeCheckIntegration),
30
29
  },
31
30
  };
32
31
 
@@ -90,7 +89,7 @@ async function getMerger(cms, basePath) {
90
89
  /**
91
90
  * Inject CMS integration using smart merge
92
91
  * @param {string} targetDir - The project directory
93
- * @param {string} cms - The CMS name (e.g., 'sanity', 'basehub')
92
+ * @param {string} cms - The CMS name (e.g., 'sanity')
94
93
  * @param {object} spinner - Ora spinner for progress
95
94
  * @returns {object} Results of the merge operation
96
95
  */
@@ -108,12 +107,12 @@ export async function injectIntegration(targetDir, cms, spinner) {
108
107
  if (!cmsConfig) {
109
108
  results.failed.push({
110
109
  path: cms,
111
- error: `CMS "${cms}" is not supported. Supported: sanity, basehub`,
110
+ error: `CMS "${cms}" is not supported. Supported: sanity`,
112
111
  });
113
112
  return results;
114
113
  }
115
114
 
116
- const { branch, mergeFiles, additivePaths } = cmsConfig;
115
+ const { mergeFiles, additivePaths } = cmsConfig;
117
116
 
118
117
  // Detect if project uses src/ prefix (e.g., src/app/ instead of app/)
119
118
  const pathPrefix = await detectPathPrefix(targetDir);
@@ -121,111 +120,103 @@ export async function injectIntegration(targetDir, cms, spinner) {
121
120
  spinner.text = `Detected src/ directory structure...`;
122
121
  }
123
122
 
124
- // 1. Clone integration to temp directory
125
- const tempDir = path.join(os.tmpdir(), `integration-${Date.now()}`);
126
-
127
- try {
128
- spinner.text = `Downloading ${cms} integration...`;
123
+ // Resolve local integration files directory
124
+ const integrationDir = path.resolve(
125
+ __dirname,
126
+ "../../integrations",
127
+ cms,
128
+ "files",
129
+ );
129
130
 
130
- // Use CMS-specific branch
131
- const integrationRepoPath = `github:basementstudio/basement-cli/integrations/${cms}#${branch}`;
132
- const emitter = tiged(integrationRepoPath, {
133
- cache: false,
134
- force: true,
135
- mode: "git",
131
+ if (!(await fs.pathExists(integrationDir))) {
132
+ results.failed.push({
133
+ path: cms,
134
+ error: `Integration directory not found: ${integrationDir}`,
136
135
  });
136
+ return results;
137
+ }
137
138
 
138
- await emitter.clone(tempDir);
139
-
140
- // 2. Copy additive files/directories (CMS-specific paths)
141
- spinner.text = `Adding ${cms} files...`;
139
+ // 1. Copy additive files/directories (CMS-specific paths)
140
+ spinner.text = `Adding ${cms} files...`;
142
141
 
143
- for (const additivePath of additivePaths) {
144
- const src = path.join(tempDir, additivePath);
145
- // Transform destination path based on project structure
146
- const transformedPath = transformPath(additivePath, pathPrefix);
147
- const dest = path.join(targetDir, transformedPath);
142
+ for (const additivePath of additivePaths) {
143
+ const src = path.join(integrationDir, additivePath);
144
+ // Transform destination path based on project structure
145
+ const transformedPath = transformPath(additivePath, pathPrefix);
146
+ const dest = path.join(targetDir, transformedPath);
148
147
 
149
- try {
150
- if (await fs.pathExists(src)) {
151
- await fs.copy(src, dest, { overwrite: false });
152
- results.copied.push(transformedPath);
153
- }
154
- } catch (error) {
155
- results.failed.push({ path: transformedPath, error: error.message });
148
+ try {
149
+ if (await fs.pathExists(src)) {
150
+ await fs.copy(src, dest, { overwrite: false });
151
+ results.copied.push(transformedPath);
156
152
  }
153
+ } catch (error) {
154
+ results.failed.push({ path: transformedPath, error: error.message });
157
155
  }
156
+ }
158
157
 
159
- // 3. Smart merge files that exist in both (CMS-specific mergers)
160
- if (mergeFiles.length > 0) {
161
- spinner.text = `Merging ${cms} integration...`;
162
-
163
- for (const mergeFile of mergeFiles) {
164
- // Transform path for the target project
165
- const transformedPath = transformPath(mergeFile, pathPrefix);
166
- const templateFile = path.join(targetDir, transformedPath);
167
- const integrationFile = path.join(tempDir, mergeFile);
168
-
169
- try {
170
- // Check if template file exists
171
- if (!(await fs.pathExists(templateFile))) {
172
- // Template doesn't have this file - copy from integration
173
- if (await fs.pathExists(integrationFile)) {
174
- await fs.ensureDir(path.dirname(templateFile));
175
- await fs.copy(integrationFile, templateFile);
176
- results.copied.push(transformedPath);
177
- } else {
178
- results.skipped.push({
179
- path: transformedPath,
180
- reason: "Not in integration",
181
- });
182
- }
183
- continue;
184
- }
158
+ // 2. Smart merge files that exist in both (CMS-specific mergers)
159
+ if (mergeFiles.length > 0) {
160
+ spinner.text = `Merging ${cms} integration...`;
161
+
162
+ for (const mergeFile of mergeFiles) {
163
+ // Transform path for the target project
164
+ const transformedPath = transformPath(mergeFile, pathPrefix);
165
+ const templateFile = path.join(targetDir, transformedPath);
166
+ const integrationFile = path.join(integrationDir, mergeFile);
185
167
 
186
- // Check if integration file exists
187
- if (!(await fs.pathExists(integrationFile))) {
168
+ try {
169
+ // Check if template file exists
170
+ if (!(await fs.pathExists(templateFile))) {
171
+ // Template doesn't have this file - copy from integration
172
+ if (await fs.pathExists(integrationFile)) {
173
+ await fs.ensureDir(path.dirname(templateFile));
174
+ await fs.copy(integrationFile, templateFile);
175
+ results.copied.push(transformedPath);
176
+ } else {
188
177
  results.skipped.push({
189
178
  path: transformedPath,
190
179
  reason: "Not in integration",
191
180
  });
192
- continue;
193
181
  }
182
+ continue;
183
+ }
194
184
 
195
- // Get the CMS-specific merger function (use base path for lookup)
196
- const merger = await getMerger(cms, mergeFile);
185
+ // Check if integration file exists
186
+ if (!(await fs.pathExists(integrationFile))) {
187
+ results.skipped.push({
188
+ path: transformedPath,
189
+ reason: "Not in integration",
190
+ });
191
+ continue;
192
+ }
197
193
 
198
- if (merger) {
199
- const result = await merger(templateFile, {
200
- targetDir,
201
- pathPrefix,
202
- });
203
- if (result.skipped) {
204
- results.skipped.push({
205
- path: transformedPath,
206
- reason: result.reason,
207
- });
208
- } else {
209
- results.merged.push(transformedPath);
210
- }
211
- } else {
194
+ // Get the CMS-specific merger function (use base path for lookup)
195
+ const merger = await getMerger(cms, mergeFile);
196
+
197
+ if (merger) {
198
+ const result = await merger(templateFile, {
199
+ targetDir,
200
+ pathPrefix,
201
+ });
202
+ if (result.skipped) {
212
203
  results.skipped.push({
213
204
  path: transformedPath,
214
- reason: `No merger for ${cms}`,
205
+ reason: result.reason,
215
206
  });
207
+ } else {
208
+ results.merged.push(transformedPath);
216
209
  }
217
- } catch (error) {
218
- results.failed.push({ path: transformedPath, error: error.message });
210
+ } else {
211
+ results.skipped.push({
212
+ path: transformedPath,
213
+ reason: `No merger for ${cms}`,
214
+ });
219
215
  }
216
+ } catch (error) {
217
+ results.failed.push({ path: transformedPath, error: error.message });
220
218
  }
221
219
  }
222
- } finally {
223
- // 4. Cleanup temp directory
224
- try {
225
- await fs.remove(tempDir);
226
- } catch {
227
- // Ignore cleanup errors
228
- }
229
220
  }
230
221
 
231
222
  return results;
@@ -253,8 +244,8 @@ export async function injectLayer(targetDir, layer, spinner) {
253
244
  return results;
254
245
  }
255
246
 
256
- // Resolve the layer directory from the CLI repo's layers/{type}/ path
257
- const layerDir = path.resolve(__dirname, "../../layers", layer);
247
+ // Resolve the layer directory from create-basement-app's templates/{type}/ path
248
+ const layerDir = path.resolve(__dirname, "../../templates", layer);
258
249
 
259
250
  if (!(await fs.pathExists(layerDir))) {
260
251
  results.skipped.push({
@@ -0,0 +1,223 @@
1
+ // packages/create-basement-app/src/mergers/layout-merger.js
2
+
3
+ import path from "node:path";
4
+ import fs from "fs-extra";
5
+
6
+ /**
7
+ * Sanity-specific imports to inject into layout.
8
+ * Only two lightweight components — no dynamic functions (draftMode, headers, etc.)
9
+ */
10
+ const SANITY_IMPORTS = `import { SanityStudioGuard } from "@/components/sanity/studio-guard"
11
+ import { SanityVisualEditing } from "@/components/sanity/visual-editing"`;
12
+
13
+ /**
14
+ * Client component that hides site chrome on /studio routes.
15
+ * Uses useSelectedLayoutSegment() — a stable Next.js client API.
16
+ */
17
+ const STUDIO_GUARD_COMPONENT = `"use client"
18
+
19
+ import { useSelectedLayoutSegment } from "next/navigation"
20
+
21
+ export function SanityStudioGuard({ children }: { children: React.ReactNode }) {
22
+ const segment = useSelectedLayoutSegment()
23
+ if (segment === "studio") return null
24
+ return <>{children}</>
25
+ }
26
+ `;
27
+
28
+ /**
29
+ * Server component that handles Sanity visual editing.
30
+ * Keeps draftMode() inside its own <Suspense> boundary so the root layout
31
+ * never calls dynamic functions directly — preserving static rendering.
32
+ */
33
+ const VISUAL_EDITING_COMPONENT = `import { Suspense } from "react"
34
+ import { draftMode } from "next/headers"
35
+ import { VisualEditing } from "next-sanity/visual-editing"
36
+ import { isSanityConfigured } from "@/lib/integrations/check-integration"
37
+ import { SanityLive } from "@/lib/integrations/sanity/live"
38
+
39
+ async function VisualEditingInner() {
40
+ const { isEnabled: isDraftMode } = await draftMode()
41
+ const sanityConfigured = isSanityConfigured()
42
+
43
+ if (!sanityConfigured || !isDraftMode) return null
44
+
45
+ return (
46
+ <>
47
+ <VisualEditing />
48
+ <SanityLive />
49
+ </>
50
+ )
51
+ }
52
+
53
+ export function SanityVisualEditing() {
54
+ return (
55
+ <Suspense fallback={null}>
56
+ <VisualEditingInner />
57
+ </Suspense>
58
+ )
59
+ }
60
+ `;
61
+
62
+ /**
63
+ * Merge Sanity integration into template layout.
64
+ * The layout itself stays free of dynamic functions (draftMode, headers, etc.)
65
+ * All dynamic behavior is encapsulated in generated components.
66
+ *
67
+ * Strategy:
68
+ * 1. Inject two imports (SanityStudioGuard, SanityVisualEditing)
69
+ * 2. Wrap body chrome in <SanityStudioGuard>
70
+ * 3. Add <SanityVisualEditing /> before </body>
71
+ * 4. Generate the two component files
72
+ *
73
+ * @param {string} templatePath - Path to the template's layout.tsx
74
+ * @param {object} options - Options from the merge orchestrator
75
+ * @param {string} options.targetDir - The project root directory
76
+ * @param {string} options.pathPrefix - Path prefix (e.g., 'src/' or '')
77
+ * @returns {Promise<{skipped?: boolean, reason?: string, success?: boolean}>}
78
+ */
79
+ export async function mergeLayout(
80
+ templatePath,
81
+ { targetDir, pathPrefix } = {},
82
+ ) {
83
+ let content = await fs.readFile(templatePath, "utf-8");
84
+
85
+ // Skip if already has Sanity integration
86
+ if (
87
+ content.includes("SanityStudioGuard") ||
88
+ content.includes("isSanityConfigured")
89
+ ) {
90
+ return { skipped: true, reason: "Already has Sanity integration" };
91
+ }
92
+
93
+ // 1. Add Sanity imports after existing imports
94
+ const importMatches = [...content.matchAll(/^import .+$/gm)];
95
+ if (importMatches.length > 0) {
96
+ const lastImport = importMatches[importMatches.length - 1];
97
+ const insertPos = lastImport.index + lastImport[0].length;
98
+ content =
99
+ content.slice(0, insertPos) +
100
+ "\n" +
101
+ SANITY_IMPORTS +
102
+ content.slice(insertPos);
103
+ }
104
+
105
+ // 2. Ensure Suspense is imported from react
106
+ if (!content.match(/import\s+.*Suspense.*from\s+["']react["']/)) {
107
+ const reactImportMatch = content.match(
108
+ /import\s+\{([^}]*)\}\s+from\s+["']react["']/,
109
+ );
110
+ if (reactImportMatch) {
111
+ if (!reactImportMatch[1].includes("Suspense")) {
112
+ content = content.replace(
113
+ reactImportMatch[0],
114
+ reactImportMatch[0].replace(
115
+ reactImportMatch[1],
116
+ `${reactImportMatch[1].trim()}, Suspense`,
117
+ ),
118
+ );
119
+ }
120
+ } else {
121
+ const lastImportMatch = content.match(/^(import\s+.+\n)/gm);
122
+ if (lastImportMatch) {
123
+ const lastImport = lastImportMatch[lastImportMatch.length - 1];
124
+ content = content.replace(
125
+ lastImport,
126
+ `${lastImport}import { Suspense } from "react"\n`,
127
+ );
128
+ }
129
+ }
130
+ }
131
+
132
+ // 3. Wrap body chrome in <SanityStudioGuard> and add <SanityVisualEditing />
133
+ content = content.replace(
134
+ /(\s*)(<body[^>]*>)([\s\S]*?)(\{children\})([\s\S]*?)(<\/body>)/m,
135
+ (
136
+ _match,
137
+ bodyIndent,
138
+ bodyOpen,
139
+ beforeChildren,
140
+ _childrenExpr,
141
+ afterChildren,
142
+ bodyClose,
143
+ ) => {
144
+ const baseIndent = bodyIndent || " ";
145
+ const indent = `${baseIndent} `;
146
+ const innerIndent = `${indent} `;
147
+
148
+ const trimmedBefore = beforeChildren.trim();
149
+ const trimmedAfter = afterChildren.trim();
150
+ const hasChrome = trimmedBefore.length > 0 || trimmedAfter.length > 0;
151
+
152
+ // Preserve relative indentation of chrome content
153
+ const reindent = (text, targetIndent) => {
154
+ const lines = text.split("\n").filter((line) => line.trim());
155
+ if (lines.length === 0) return "";
156
+ const minIndent = Math.min(
157
+ ...lines
158
+ .map((line) => line.match(/^(\s*)/)[1].length)
159
+ .filter((n) => n > 0),
160
+ Infinity,
161
+ );
162
+ return lines
163
+ .map((line) => {
164
+ const currentIndent = line.match(/^(\s*)/)[1].length;
165
+ const relativeIndent = Math.max(
166
+ 0,
167
+ currentIndent - (minIndent === Infinity ? 0 : minIndent),
168
+ );
169
+ return targetIndent + " ".repeat(relativeIndent) + line.trim();
170
+ })
171
+ .join("\n");
172
+ };
173
+
174
+ let result = `${baseIndent}${bodyOpen}\n`;
175
+
176
+ if (hasChrome && trimmedBefore) {
177
+ result += `${indent}<Suspense fallback={null}>\n`;
178
+ result += `${innerIndent}<SanityStudioGuard>\n`;
179
+ result += reindent(trimmedBefore, `${innerIndent} `);
180
+ result += `\n${innerIndent}</SanityStudioGuard>\n`;
181
+ result += `${indent}</Suspense>\n\n`;
182
+ }
183
+
184
+ result += `${indent}{children}\n`;
185
+
186
+ if (hasChrome && trimmedAfter) {
187
+ result += `\n${indent}<Suspense fallback={null}>\n`;
188
+ result += `${innerIndent}<SanityStudioGuard>\n`;
189
+ result += reindent(trimmedAfter, `${innerIndent} `);
190
+ result += `\n${innerIndent}</SanityStudioGuard>\n`;
191
+ result += `${indent}</Suspense>\n`;
192
+ }
193
+
194
+ result += `\n${indent}<SanityVisualEditing />\n${baseIndent}${bodyClose}`;
195
+ return result;
196
+ },
197
+ );
198
+
199
+ await fs.writeFile(templatePath, content);
200
+
201
+ // 3. Generate component files
202
+ if (targetDir) {
203
+ const sanityDir = path.join(
204
+ targetDir,
205
+ pathPrefix || "",
206
+ "components",
207
+ "sanity",
208
+ );
209
+ await fs.ensureDir(sanityDir);
210
+
211
+ const guardPath = path.join(sanityDir, "studio-guard.tsx");
212
+ if (!(await fs.pathExists(guardPath))) {
213
+ await fs.writeFile(guardPath, STUDIO_GUARD_COMPONENT);
214
+ }
215
+
216
+ const visualEditingPath = path.join(sanityDir, "visual-editing.tsx");
217
+ if (!(await fs.pathExists(visualEditingPath))) {
218
+ await fs.writeFile(visualEditingPath, VISUAL_EDITING_COMPONENT);
219
+ }
220
+ }
221
+
222
+ return { success: true };
223
+ }
@@ -0,0 +1,121 @@
1
+ // packages/create-basement-app/src/mergers/sitemap-merger.js
2
+ import fs from "fs-extra";
3
+
4
+ /**
5
+ * Sanity-specific import to add
6
+ */
7
+ const SANITY_IMPORT = `import { isSanityConfigured } from "@/lib/integrations/check-integration"`;
8
+
9
+ /**
10
+ * Sanity fetch logic to inject before return statement
11
+ * Uses early return pattern and spread operator for cleaner code
12
+ */
13
+ const SANITY_FETCH_LOGIC = `
14
+ // Only fetch Sanity pages if Sanity is configured
15
+ if (isSanityConfigured()) {
16
+ try {
17
+ const sanityModule = await import("@/lib/integrations/sanity/client")
18
+ const sanityGroq = await import("next-sanity")
19
+
20
+ const client = sanityModule?.client
21
+ const groq = sanityGroq?.groq
22
+
23
+ // Skip if client is null (shouldn't happen since we check isSanityConfigured)
24
+ if (!(client && groq)) return baseRoutes
25
+
26
+ type SanityDocument = {
27
+ slug: { current: string }
28
+ _updatedAt: string
29
+ metadata?: { noIndex?: boolean }
30
+ }
31
+
32
+ // Fetch all published pages and articles
33
+ const pages = (await client.fetch(
34
+ groq\`*[_type == "page" && defined(slug.current)] {
35
+ slug,
36
+ _updatedAt,
37
+ metadata
38
+ }\`
39
+ )) as SanityDocument[]
40
+
41
+ const articles = (await client.fetch(
42
+ groq\`*[_type == "article" && defined(slug.current)] {
43
+ slug,
44
+ _updatedAt,
45
+ metadata
46
+ }\`
47
+ )) as SanityDocument[]
48
+
49
+ // Add pages to sitemap (exclude noIndex pages)
50
+ const pageEntries: MetadataRoute.Sitemap = pages
51
+ .filter((page: SanityDocument) => !page.metadata?.noIndex)
52
+ .map((page: SanityDocument) => ({
53
+ url: \`\${APP_BASE_URL}/\${page.slug.current}\`,
54
+ lastModified: new Date(page._updatedAt),
55
+ changeFrequency: "weekly" as const,
56
+ priority: 0.8,
57
+ }))
58
+
59
+ // Add articles to sitemap (exclude noIndex articles)
60
+ const articleEntries: MetadataRoute.Sitemap = articles
61
+ .filter((article: SanityDocument) => !article.metadata?.noIndex)
62
+ .map((article: SanityDocument) => ({
63
+ url: \`\${APP_BASE_URL}/blog/\${article.slug.current}\`,
64
+ lastModified: new Date(article._updatedAt),
65
+ changeFrequency: "weekly" as const,
66
+ priority: 0.7,
67
+ }))
68
+
69
+ return [...baseRoutes, ...pageEntries, ...articleEntries]
70
+ } catch (error) {
71
+ console.error("Error generating sitemap from Sanity:", error)
72
+ return baseRoutes
73
+ }
74
+ }
75
+
76
+ `;
77
+
78
+ /**
79
+ * Merge Sanity integration into template sitemap
80
+ * Preserves all existing agnostic code (routes, baseRoutes structure)
81
+ * and injects Sanity-specific fetching logic
82
+ *
83
+ * @param {string} templatePath - Path to the template's sitemap.ts
84
+ * @returns {Promise<{skipped?: boolean, reason?: string, success?: boolean}>}
85
+ */
86
+ export async function mergeSitemap(templatePath) {
87
+ let content = await fs.readFile(templatePath, "utf-8");
88
+
89
+ // Skip if already has Sanity integration
90
+ if (content.includes("isSanityConfigured")) {
91
+ return { skipped: true, reason: "Already has Sanity integration" };
92
+ }
93
+
94
+ // 1. Add Sanity import after existing imports
95
+ const importMatches = [...content.matchAll(/^import .+$/gm)];
96
+ if (importMatches.length > 0) {
97
+ const lastImport = importMatches[importMatches.length - 1];
98
+ const insertPos = lastImport.index + lastImport[0].length;
99
+ content =
100
+ content.slice(0, insertPos) +
101
+ "\n" +
102
+ SANITY_IMPORT +
103
+ content.slice(insertPos);
104
+ }
105
+
106
+ // 2. Add Sanity fetch logic before the final return statement
107
+ // Find the last "return baseRoutes" or "return [...baseRoutes" pattern
108
+ const returnMatch = content.match(
109
+ /(\n)([ \t]*)(return\s+(?:baseRoutes|\[\.\.\.baseRoutes))/m,
110
+ );
111
+ if (returnMatch) {
112
+ const returnIndex = content.indexOf(returnMatch[0]);
113
+ content =
114
+ content.slice(0, returnIndex) +
115
+ SANITY_FETCH_LOGIC +
116
+ content.slice(returnIndex);
117
+ }
118
+
119
+ await fs.writeFile(templatePath, content);
120
+ return { success: true };
121
+ }
@@ -0,0 +1,38 @@
1
+ // packages/create-basement-app/template-hooks/config.js
2
+
3
+ /**
4
+ * Hook configuration manifest.
5
+ * Each entry describes a template hook, its source file, and npm dependencies.
6
+ */
7
+ export const hooksConfig = [
8
+ {
9
+ name: "use-battery",
10
+ file: "use-battery.ts",
11
+ dependencies: {
12
+ "lodash-es": "^4.17.21",
13
+ },
14
+ devDependencies: {},
15
+ },
16
+ {
17
+ name: "use-device-perf",
18
+ file: "use-device-perf.ts",
19
+ dependencies: {
20
+ "detect-gpu": "^5.0.0",
21
+ "react-device-detect": "^2.2.3",
22
+ zustand: "^5.0.0",
23
+ },
24
+ devDependencies: {},
25
+ },
26
+ {
27
+ name: "use-intersection-observer",
28
+ file: "use-intersection-observer.ts",
29
+ dependencies: {},
30
+ devDependencies: {},
31
+ },
32
+ {
33
+ name: "use-media",
34
+ file: "use-media.ts",
35
+ dependencies: {},
36
+ devDependencies: {},
37
+ },
38
+ ];
@@ -0,0 +1,21 @@
1
+ ## Plugins
2
+
3
+ ### 1. `no-anchor-element.grit`
4
+ Enforces using Next.js `<Link>` component instead of HTML `<a>` elements.
5
+
6
+ ### 2. `no-unnecessary-forwardref.grit`
7
+ Checks for unnecessary `forwardRef` usage in React 19 with the compiler.
8
+
9
+ ### 3. `no-relative-parent-imports.grit`
10
+ Forbids relative parent imports (`../`) and encourages alias imports (`@/`).
11
+
12
+ ## Plugin Configuration
13
+
14
+ The plugins are configured in `biome.json`:
15
+ ```json
16
+ "plugins": [
17
+ "./biome-plugins/no-anchor-element.grit",
18
+ "./biome-plugins/no-unnecessary-forwardref.grit",
19
+ "./biome-plugins/no-relative-parent-imports.grit"
20
+ ]
21
+ ```
@@ -0,0 +1,12 @@
1
+ language js;
2
+
3
+ `<a $attrs>$content</a>` as $anchor where {
4
+ !$anchor <: within `if ($condition) { return ($jsx) }` where {
5
+ $condition <: contains or { `isExternal`, `isExternalSSR` }
6
+ },
7
+ register_diagnostic(
8
+ span = $anchor,
9
+ message = "Use custom Link component instead of <a> element. The Link component handles both internal and external links automatically.",
10
+ severity = "error"
11
+ )
12
+ }
@@ -0,0 +1,10 @@
1
+ language js;
2
+
3
+ `import $imports from $source` as $import where {
4
+ $source <: r"['\"]\.\.\/\.\.\/.*['\"]",
5
+ register_diagnostic(
6
+ span = $import,
7
+ message = "Use alias imports (~/dir/) instead of deep relative imports (../../). Single level imports (../) are allowed for colocated files.",
8
+ severity = "error"
9
+ )
10
+ }