@nextsparkjs/core 0.1.0-beta.187 → 0.1.0-beta.189

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 (414) hide show
  1. package/dist/components/api/keys/ApiKeyDisplay.js +19 -19
  2. package/dist/components/api/keys/CreateApiKeyDialog.js +21 -21
  3. package/dist/components/app/layouts/PublicNavbar.d.ts.map +1 -1
  4. package/dist/components/app/layouts/PublicNavbar.js +34 -16
  5. package/dist/components/app/misc/ThemeToggle.d.ts.map +1 -1
  6. package/dist/components/app/misc/ThemeToggle.js +3 -4
  7. package/dist/components/auth/SessionCookieRefresher.d.ts +5 -0
  8. package/dist/components/auth/SessionCookieRefresher.d.ts.map +1 -1
  9. package/dist/components/auth/SessionCookieRefresher.js +7 -1
  10. package/dist/components/auth/forms/LoginForm.d.ts.map +1 -1
  11. package/dist/components/auth/forms/LoginForm.js +49 -12
  12. package/dist/components/auth/forms/SignupForm.d.ts.map +1 -1
  13. package/dist/components/auth/forms/SignupForm.js +4 -3
  14. package/dist/components/dashboard/block-editor/block-settings-panel.js +1 -1
  15. package/dist/components/dashboard/block-editor/floating-block-toolbar.js +1 -1
  16. package/dist/components/dashboard/block-editor/sortable-block.js +1 -1
  17. package/dist/components/dashboard/layouts/Sidebar.d.ts.map +1 -1
  18. package/dist/components/dashboard/layouts/Sidebar.js +8 -6
  19. package/dist/components/dashboard/layouts/TopNavbar.d.ts.map +1 -1
  20. package/dist/components/dashboard/layouts/TopNavbar.js +51 -54
  21. package/dist/components/dashboard/misc/NotificationsDropdown.d.ts.map +1 -1
  22. package/dist/components/dashboard/misc/NotificationsDropdown.js +17 -15
  23. package/dist/components/dashboard/misc/SearchDropdown.d.ts.map +1 -1
  24. package/dist/components/dashboard/misc/SearchDropdown.js +19 -22
  25. package/dist/components/dashboard/mobile/MobileBottomNav.js +1 -1
  26. package/dist/components/dashboard/mobile/MobileTopBar.d.ts +10 -1
  27. package/dist/components/dashboard/mobile/MobileTopBar.d.ts.map +1 -1
  28. package/dist/components/dashboard/mobile/MobileTopBar.js +4 -3
  29. package/dist/components/dashboard/navigation/DynamicNavigation.js +5 -5
  30. package/dist/components/devtools/BlockDetailViewer.js +1 -1
  31. package/dist/components/devtools/BlocksViewer.js +1 -1
  32. package/dist/components/entities/EntityChildManager.js +5 -5
  33. package/dist/components/entities/EntityDetailPage.d.ts.map +1 -1
  34. package/dist/components/entities/EntityDetailPage.js +16 -15
  35. package/dist/components/entities/EntityFieldRenderer.d.ts.map +1 -1
  36. package/dist/components/entities/EntityFieldRenderer.js +12 -7
  37. package/dist/components/entities/EntityPageHeader.js +5 -5
  38. package/dist/components/entities/ErrorBoundary.d.ts.map +1 -1
  39. package/dist/components/entities/ErrorBoundary.js +20 -21
  40. package/dist/components/entities/wrappers/EntityDetailWrapper.js +5 -5
  41. package/dist/components/entities/wrappers/EntityFormWrapper.js +2 -2
  42. package/dist/components/entities/wrappers/EntityListWrapper.js +3 -3
  43. package/dist/components/permissions/NoPermission.js +10 -10
  44. package/dist/components/public/pageBuilder/PageRenderer.d.ts.map +1 -1
  45. package/dist/components/public/pageBuilder/PageRenderer.js +2 -1
  46. package/dist/components/settings/SettingsPageSkeleton.js +4 -4
  47. package/dist/components/settings/layouts/SettingsSidebar.js +6 -6
  48. package/dist/components/shared/MultiSelectFilter.d.ts +3 -1
  49. package/dist/components/shared/MultiSelectFilter.d.ts.map +1 -1
  50. package/dist/components/shared/MultiSelectFilter.js +7 -3
  51. package/dist/components/shared/SearchInput.d.ts +2 -0
  52. package/dist/components/shared/SearchInput.d.ts.map +1 -1
  53. package/dist/components/shared/SearchInput.js +64 -6
  54. package/dist/components/ui/address-input.js +18 -18
  55. package/dist/components/ui/audio-upload.js +9 -9
  56. package/dist/components/ui/combobox.d.ts.map +1 -1
  57. package/dist/components/ui/combobox.js +4 -4
  58. package/dist/components/ui/country-select.d.ts.map +1 -1
  59. package/dist/components/ui/country-select.js +3 -3
  60. package/dist/components/ui/currency-select.d.ts.map +1 -1
  61. package/dist/components/ui/currency-select.js +3 -3
  62. package/dist/components/ui/dynamic-icon.d.ts.map +1 -1
  63. package/dist/components/ui/dynamic-icon.js +3 -2
  64. package/dist/components/ui/file-upload.js +7 -7
  65. package/dist/components/ui/image-upload.js +8 -8
  66. package/dist/components/ui/multi-select.d.ts +2 -1
  67. package/dist/components/ui/multi-select.d.ts.map +1 -1
  68. package/dist/components/ui/multi-select.js +4 -3
  69. package/dist/components/ui/phone-input.d.ts.map +1 -1
  70. package/dist/components/ui/phone-input.js +3 -3
  71. package/dist/components/ui/relation-display.js +1 -1
  72. package/dist/components/ui/simple-relation-select.d.ts +2 -1
  73. package/dist/components/ui/simple-relation-select.d.ts.map +1 -1
  74. package/dist/components/ui/simple-relation-select.js +7 -6
  75. package/dist/components/ui/tags-input.d.ts.map +1 -1
  76. package/dist/components/ui/tags-input.js +1 -1
  77. package/dist/components/ui/timezone-select.d.ts.map +1 -1
  78. package/dist/components/ui/timezone-select.js +3 -3
  79. package/dist/components/ui/user-display.js +4 -4
  80. package/dist/components/ui/user-select.d.ts.map +1 -1
  81. package/dist/components/ui/user-select.js +9 -9
  82. package/dist/components/ui/video-upload.js +9 -9
  83. package/dist/contexts/SubscriptionContext.d.ts.map +1 -1
  84. package/dist/contexts/SubscriptionContext.js +5 -1
  85. package/dist/contexts/TeamContext.d.ts.map +1 -1
  86. package/dist/contexts/TeamContext.js +83 -26
  87. package/dist/emails/otp-verification.d.ts.map +1 -1
  88. package/dist/emails/otp-verification.js +3 -2
  89. package/dist/hooks/useAccountPreferencesSync.d.ts +13 -0
  90. package/dist/hooks/useAccountPreferencesSync.d.ts.map +1 -0
  91. package/dist/hooks/useAccountPreferencesSync.js +95 -0
  92. package/dist/hooks/useAuth.d.ts +202 -15
  93. package/dist/hooks/useAuth.d.ts.map +1 -1
  94. package/dist/hooks/useAuth.js +49 -13
  95. package/dist/hooks/useEntityConfig.js +2 -2
  96. package/dist/hooks/useEntitySearch.js +10 -10
  97. package/dist/hooks/useNotifications.js +12 -12
  98. package/dist/hooks/usePrefetch.d.ts.map +1 -1
  99. package/dist/hooks/usePrefetch.js +6 -2
  100. package/dist/hooks/useSearch.js +15 -15
  101. package/dist/hooks/useSessionCookieRefresh.d.ts +18 -1
  102. package/dist/hooks/useSessionCookieRefresh.d.ts.map +1 -1
  103. package/dist/hooks/useSessionCookieRefresh.js +17 -4
  104. package/dist/hooks/useSessionHint.d.ts +12 -0
  105. package/dist/hooks/useSessionHint.d.ts.map +1 -0
  106. package/dist/hooks/useSessionHint.js +15 -0
  107. package/dist/hooks/useTranslationPreload.js +6 -6
  108. package/dist/i18n.d.ts +13 -0
  109. package/dist/i18n.d.ts.map +1 -1
  110. package/dist/i18n.js +2 -27
  111. package/dist/lib/actions/entity.actions.d.ts.map +1 -1
  112. package/dist/lib/actions/entity.actions.js +3 -2
  113. package/dist/lib/api/auth/dual-auth.d.ts +4 -1
  114. package/dist/lib/api/auth/dual-auth.d.ts.map +1 -1
  115. package/dist/lib/api/auth/dual-auth.js +5 -16
  116. package/dist/lib/api/entities.js +33 -33
  117. package/dist/lib/api/keys.d.ts +27 -27
  118. package/dist/lib/api/keys.js +27 -27
  119. package/dist/lib/auth/callback-url.d.ts +11 -0
  120. package/dist/lib/auth/callback-url.d.ts.map +1 -0
  121. package/dist/lib/auth/callback-url.js +14 -0
  122. package/dist/lib/auth/otp-config.d.ts +55 -0
  123. package/dist/lib/auth/otp-config.d.ts.map +1 -0
  124. package/dist/lib/auth/otp-config.js +48 -0
  125. package/dist/lib/auth/registration-helpers.d.ts.map +1 -1
  126. package/dist/lib/auth/registration-helpers.js +3 -1
  127. package/dist/lib/auth/session-hint.d.ts +31 -0
  128. package/dist/lib/auth/session-hint.d.ts.map +1 -0
  129. package/dist/lib/auth/session-hint.js +37 -0
  130. package/dist/lib/auth.d.ts.map +1 -1
  131. package/dist/lib/auth.js +6 -4
  132. package/dist/lib/blocks/loader.d.ts +3 -11
  133. package/dist/lib/blocks/loader.d.ts.map +1 -1
  134. package/dist/lib/blocks/loader.js +1 -9
  135. package/dist/lib/blocks/loader.server.d.ts +18 -0
  136. package/dist/lib/blocks/loader.server.d.ts.map +1 -0
  137. package/dist/lib/blocks/loader.server.js +11 -0
  138. package/dist/lib/blocks/sanitize-html.d.ts +31 -0
  139. package/dist/lib/blocks/sanitize-html.d.ts.map +1 -0
  140. package/dist/lib/blocks/sanitize-html.js +60 -0
  141. package/dist/lib/config/config-sync.d.ts +1 -0
  142. package/dist/lib/config/config-sync.d.ts.map +1 -1
  143. package/dist/lib/config/config-sync.js +4 -1
  144. package/dist/lib/config/types.d.ts +48 -0
  145. package/dist/lib/config/types.d.ts.map +1 -1
  146. package/dist/lib/email/types.d.ts +6 -0
  147. package/dist/lib/email/types.d.ts.map +1 -1
  148. package/dist/lib/entities/serialization.d.ts.map +1 -1
  149. package/dist/lib/entities/serialization.js +3 -6
  150. package/dist/lib/icons.d.ts +12 -0
  151. package/dist/lib/icons.d.ts.map +1 -0
  152. package/dist/lib/icons.js +30 -0
  153. package/dist/lib/locale.d.ts +25 -1
  154. package/dist/lib/locale.d.ts.map +1 -1
  155. package/dist/lib/locale.js +49 -35
  156. package/dist/lib/mcp/engine.js +1 -1
  157. package/dist/lib/mcp/errors.js +17 -17
  158. package/dist/lib/mcp/schema-builder.js +16 -16
  159. package/dist/lib/mcp/tool-generator.js +18 -18
  160. package/dist/lib/search-highlighting.d.ts +7 -0
  161. package/dist/lib/search-highlighting.d.ts.map +1 -1
  162. package/dist/lib/search-highlighting.js +6 -2
  163. package/dist/lib/selectors/core-selectors.d.ts +2 -0
  164. package/dist/lib/selectors/core-selectors.d.ts.map +1 -1
  165. package/dist/lib/selectors/domains/auth.selectors.d.ts +1 -0
  166. package/dist/lib/selectors/domains/auth.selectors.d.ts.map +1 -1
  167. package/dist/lib/selectors/domains/auth.selectors.js +1 -0
  168. package/dist/lib/selectors/domains/entities.selectors.d.ts +2 -0
  169. package/dist/lib/selectors/domains/entities.selectors.d.ts.map +1 -1
  170. package/dist/lib/selectors/domains/entities.selectors.js +2 -0
  171. package/dist/lib/selectors/selectors.d.ts +4 -0
  172. package/dist/lib/selectors/selectors.d.ts.map +1 -1
  173. package/dist/lib/services/block.service.js +1 -1
  174. package/dist/lib/teams/active-team-cookie.d.ts +24 -0
  175. package/dist/lib/teams/active-team-cookie.d.ts.map +1 -0
  176. package/dist/lib/teams/active-team-cookie.js +17 -0
  177. package/dist/lib/teams/dashboard-team.d.ts +20 -0
  178. package/dist/lib/teams/dashboard-team.d.ts.map +1 -0
  179. package/dist/lib/teams/dashboard-team.js +38 -0
  180. package/dist/lib/teams/schema.d.ts.map +1 -1
  181. package/dist/lib/teams/schema.js +1 -1
  182. package/dist/lib/template-resolver.d.ts.map +1 -1
  183. package/dist/lib/template-resolver.js +0 -1
  184. package/dist/lib/theme/get-default-theme-mode.d.ts +7 -4
  185. package/dist/lib/theme/get-default-theme-mode.d.ts.map +1 -1
  186. package/dist/lib/theme/get-default-theme-mode.js +2 -44
  187. package/dist/messages/de/auth.json +4 -2
  188. package/dist/messages/de/common.json +83 -3
  189. package/dist/messages/de/email.json +1 -1
  190. package/dist/messages/de/index.d.ts +87 -0
  191. package/dist/messages/de/index.d.ts.map +1 -1
  192. package/dist/messages/de/settings.json +5 -0
  193. package/dist/messages/en/auth.json +4 -2
  194. package/dist/messages/en/common.json +83 -3
  195. package/dist/messages/en/email.json +1 -1
  196. package/dist/messages/en/index.d.ts +86 -0
  197. package/dist/messages/en/index.d.ts.map +1 -1
  198. package/dist/messages/en/settings.json +4 -0
  199. package/dist/messages/es/auth.json +4 -2
  200. package/dist/messages/es/common.json +83 -3
  201. package/dist/messages/es/email.json +1 -1
  202. package/dist/messages/es/index.d.ts +86 -0
  203. package/dist/messages/es/index.d.ts.map +1 -1
  204. package/dist/messages/es/settings.json +4 -0
  205. package/dist/messages/fr/auth.json +4 -2
  206. package/dist/messages/fr/common.json +83 -3
  207. package/dist/messages/fr/email.json +1 -1
  208. package/dist/messages/fr/index.d.ts +87 -0
  209. package/dist/messages/fr/index.d.ts.map +1 -1
  210. package/dist/messages/fr/settings.json +5 -0
  211. package/dist/messages/it/auth.json +4 -2
  212. package/dist/messages/it/common.json +83 -3
  213. package/dist/messages/it/email.json +1 -1
  214. package/dist/messages/it/index.d.ts +87 -0
  215. package/dist/messages/it/index.d.ts.map +1 -1
  216. package/dist/messages/it/settings.json +5 -0
  217. package/dist/messages/pt/auth.json +4 -2
  218. package/dist/messages/pt/common.json +83 -3
  219. package/dist/messages/pt/email.json +1 -1
  220. package/dist/messages/pt/index.d.ts +87 -0
  221. package/dist/messages/pt/index.d.ts.map +1 -1
  222. package/dist/messages/pt/settings.json +5 -0
  223. package/dist/nextspark-registries.d.ts +54 -0
  224. package/dist/providers/DashboardProviders.d.ts +13 -10
  225. package/dist/providers/DashboardProviders.d.ts.map +1 -1
  226. package/dist/providers/DashboardProviders.js +7 -1
  227. package/dist/templates/app/(auth)/forgot-password/page.tsx +2 -2
  228. package/dist/templates/app/(auth)/reset-password/page.tsx +6 -6
  229. package/dist/templates/app/403/page.tsx +6 -6
  230. package/dist/templates/app/api/v1/teams/switch/route.ts +24 -5
  231. package/dist/templates/app/dashboard/(main)/[entity]/layout.tsx +9 -7
  232. package/dist/templates/app/dashboard/(main)/layout.tsx +4 -3
  233. package/dist/templates/app/dashboard/settings/api-keys/page.tsx +36 -34
  234. package/dist/templates/app/dashboard/settings/profile/page.tsx +7 -6
  235. package/dist/templates/app/dashboard/settings/security/page.tsx +5 -4
  236. package/dist/templates/app/layout.ppr.tsx +8 -8
  237. package/dist/templates/app/layout.tsx +12 -17
  238. package/dist/templates/blocks/cta-section/component.tsx +5 -2
  239. package/dist/templates/blocks/cta-section/config.ts +3 -2
  240. package/dist/templates/blocks/cta-section/fields.ts +2 -2
  241. package/dist/templates/blocks/cta-section/schema.ts +2 -2
  242. package/dist/templates/blocks/features-grid/component.tsx +10 -2
  243. package/dist/templates/blocks/features-grid/config.ts +3 -2
  244. package/dist/templates/blocks/features-grid/fields.ts +2 -2
  245. package/dist/templates/blocks/features-grid/schema.ts +2 -2
  246. package/dist/templates/blocks/hero/component.tsx +6 -3
  247. package/dist/templates/blocks/hero/config.ts +3 -2
  248. package/dist/templates/blocks/hero/fields.ts +2 -2
  249. package/dist/templates/blocks/hero/schema.ts +2 -2
  250. package/dist/templates/blocks/testimonials/component.tsx +6 -3
  251. package/dist/templates/blocks/testimonials/config.ts +3 -2
  252. package/dist/templates/blocks/testimonials/fields.ts +2 -2
  253. package/dist/templates/blocks/testimonials/schema.ts +2 -2
  254. package/dist/templates/blocks/text-content/component.tsx +7 -3
  255. package/dist/templates/blocks/text-content/config.ts +3 -2
  256. package/dist/templates/blocks/text-content/fields.ts +2 -2
  257. package/dist/templates/blocks/text-content/schema.ts +2 -2
  258. package/dist/templates/contents/themes/starter/blocks/cta-section/component.tsx +102 -0
  259. package/dist/templates/contents/themes/starter/blocks/cta-section/config.ts +11 -0
  260. package/dist/templates/contents/themes/starter/blocks/cta-section/fields.ts +89 -0
  261. package/dist/templates/contents/themes/starter/blocks/cta-section/index.ts +6 -0
  262. package/dist/templates/contents/themes/starter/blocks/cta-section/schema.ts +32 -0
  263. package/dist/templates/contents/themes/starter/blocks/cta-section/thumbnail.png +0 -0
  264. package/dist/templates/contents/themes/starter/blocks/features-grid/component.tsx +119 -0
  265. package/dist/templates/contents/themes/starter/blocks/features-grid/config.ts +11 -0
  266. package/dist/templates/contents/themes/starter/blocks/features-grid/fields.ts +97 -0
  267. package/dist/templates/contents/themes/starter/blocks/features-grid/index.ts +6 -0
  268. package/dist/templates/contents/themes/starter/blocks/features-grid/schema.ts +40 -0
  269. package/dist/templates/contents/themes/starter/blocks/features-grid/thumbnail.png +0 -0
  270. package/dist/templates/contents/themes/starter/blocks/hero/component.tsx +105 -0
  271. package/dist/templates/contents/themes/starter/blocks/hero/config.ts +11 -0
  272. package/dist/templates/contents/themes/starter/blocks/hero/fields.ts +60 -0
  273. package/dist/templates/contents/themes/starter/blocks/hero/index.ts +6 -0
  274. package/dist/templates/contents/themes/starter/blocks/hero/schema.ts +33 -0
  275. package/dist/templates/contents/themes/starter/blocks/hero/thumbnail.png +0 -0
  276. package/dist/templates/contents/themes/starter/blocks/testimonials/component.tsx +116 -0
  277. package/dist/templates/contents/themes/starter/blocks/testimonials/config.ts +11 -0
  278. package/dist/templates/contents/themes/starter/blocks/testimonials/fields.ts +105 -0
  279. package/dist/templates/contents/themes/starter/blocks/testimonials/index.ts +6 -0
  280. package/dist/templates/contents/themes/starter/blocks/testimonials/schema.ts +41 -0
  281. package/dist/templates/contents/themes/starter/blocks/testimonials/thumbnail.png +0 -0
  282. package/dist/templates/contents/themes/starter/blocks/text-content/component.tsx +100 -0
  283. package/dist/templates/contents/themes/starter/blocks/text-content/config.ts +11 -0
  284. package/dist/templates/contents/themes/starter/blocks/text-content/fields.ts +88 -0
  285. package/dist/templates/contents/themes/starter/blocks/text-content/index.ts +6 -0
  286. package/dist/templates/contents/themes/starter/blocks/text-content/schema.ts +30 -0
  287. package/dist/templates/contents/themes/starter/blocks/text-content/thumbnail.png +0 -0
  288. package/dist/templates/contents/themes/starter/config/app.config.ts +8 -0
  289. package/dist/templates/contents/themes/starter/entities/tasks/tasks.config.ts +7 -0
  290. package/dist/templates/contents/themes/starter/tests/cypress/e2e/_utils/devtools/access.cy.ts +9 -1
  291. package/dist/templates/contents/themes/starter/tests/cypress/e2e/api/entities/tasks/TaskAPIController.js +2 -2
  292. package/dist/templates/contents/themes/starter/tests/cypress/e2e/uat/entities/tasks/tasks-crud.cy.ts +45 -38
  293. package/dist/templates/contents/themes/starter/tests/cypress/fixtures/entities.json +16 -0
  294. package/dist/templates/contents/themes/starter/tests/cypress/src/components/DevKeyringPOM.ts +3 -1
  295. package/dist/templates/contents/themes/starter/tests/cypress/src/core/BlockEditorBasePOM.ts +3 -3
  296. package/dist/templates/contents/themes/starter/tests/cypress/src/entities/PagesPOM.ts +137 -0
  297. package/dist/templates/contents/themes/starter/tests/cypress/src/entities/index.ts +2 -0
  298. package/dist/templates/contents/themes/starter/tests/cypress/src/index.ts +1 -0
  299. package/dist/templates/contents/themes/starter/tests/cypress/src/session-helpers.ts +42 -11
  300. package/dist/templates/contents/themes/starter/tests/cypress/support/commands.ts +29 -18
  301. package/dist/templates/features/blog/blocks/post-content/component.tsx +5 -1
  302. package/dist/templates/features/blog/blocks/post-content/schema.ts +1 -1
  303. package/dist/templates/features/pages/blocks/hero/component.tsx +3 -0
  304. package/dist/templates/features/pages/blocks/hero/schema.ts +1 -1
  305. package/dist/templates/proxy.ts +138 -53
  306. package/dist/types/blocks.d.ts.map +1 -1
  307. package/dist/types/blocks.js +1 -1
  308. package/dist/types/task.types.d.ts +1 -1
  309. package/dist/types/task.types.js +7 -7
  310. package/dist/utils/dev/TranslationDebugger.js +2 -2
  311. package/package.json +14 -5
  312. package/scripts/build/registry/__tests__/block-registry.test.mjs +73 -0
  313. package/scripts/build/registry/__tests__/icons-discovery.test.mjs +130 -0
  314. package/scripts/build/registry/__tests__/identifier.test.mjs +65 -0
  315. package/scripts/build/registry/__tests__/page-generator.test.mjs +430 -0
  316. package/scripts/build/registry/__tests__/scheduled-actions-registry.test.mjs +41 -0
  317. package/scripts/build/registry/__tests__/template-registry.test.mjs +89 -0
  318. package/scripts/build/registry/discovery/icons.mjs +275 -0
  319. package/scripts/build/registry/discovery/index.mjs +1 -0
  320. package/scripts/build/registry/generators/block-registry.mjs +203 -55
  321. package/scripts/build/registry/generators/feature-registry.mjs +9 -2
  322. package/scripts/build/registry/generators/icon-registry.mjs +56 -0
  323. package/scripts/build/registry/generators/identifier.mjs +28 -0
  324. package/scripts/build/registry/generators/index.mjs +1 -0
  325. package/scripts/build/registry/generators/plugin-registry.mjs +4 -3
  326. package/scripts/build/registry/generators/scheduled-actions-registry.mjs +4 -2
  327. package/scripts/build/registry/generators/template-registry.mjs +49 -41
  328. package/scripts/build/registry/generators/theme-registry.mjs +6 -7
  329. package/scripts/build/registry/post-build/page-generator.mjs +380 -59
  330. package/scripts/build/registry.mjs +9 -1
  331. package/templates/app/(auth)/forgot-password/page.tsx +2 -2
  332. package/templates/app/(auth)/reset-password/page.tsx +6 -6
  333. package/templates/app/403/page.tsx +6 -6
  334. package/templates/app/api/v1/teams/switch/route.ts +24 -5
  335. package/templates/app/dashboard/(main)/[entity]/layout.tsx +9 -7
  336. package/templates/app/dashboard/(main)/layout.tsx +4 -3
  337. package/templates/app/dashboard/settings/api-keys/page.tsx +36 -34
  338. package/templates/app/dashboard/settings/profile/page.tsx +7 -6
  339. package/templates/app/dashboard/settings/security/page.tsx +5 -4
  340. package/templates/app/layout.ppr.tsx +8 -8
  341. package/templates/app/layout.tsx +12 -17
  342. package/templates/blocks/cta-section/component.tsx +5 -2
  343. package/templates/blocks/cta-section/config.ts +3 -2
  344. package/templates/blocks/cta-section/fields.ts +2 -2
  345. package/templates/blocks/cta-section/schema.ts +2 -2
  346. package/templates/blocks/features-grid/component.tsx +10 -2
  347. package/templates/blocks/features-grid/config.ts +3 -2
  348. package/templates/blocks/features-grid/fields.ts +2 -2
  349. package/templates/blocks/features-grid/schema.ts +2 -2
  350. package/templates/blocks/hero/component.tsx +6 -3
  351. package/templates/blocks/hero/config.ts +3 -2
  352. package/templates/blocks/hero/fields.ts +2 -2
  353. package/templates/blocks/hero/schema.ts +2 -2
  354. package/templates/blocks/testimonials/component.tsx +6 -3
  355. package/templates/blocks/testimonials/config.ts +3 -2
  356. package/templates/blocks/testimonials/fields.ts +2 -2
  357. package/templates/blocks/testimonials/schema.ts +2 -2
  358. package/templates/blocks/text-content/component.tsx +7 -3
  359. package/templates/blocks/text-content/config.ts +3 -2
  360. package/templates/blocks/text-content/fields.ts +2 -2
  361. package/templates/blocks/text-content/schema.ts +2 -2
  362. package/templates/contents/themes/starter/blocks/cta-section/component.tsx +102 -0
  363. package/templates/contents/themes/starter/blocks/cta-section/config.ts +11 -0
  364. package/templates/contents/themes/starter/blocks/cta-section/fields.ts +89 -0
  365. package/templates/contents/themes/starter/blocks/cta-section/index.ts +6 -0
  366. package/templates/contents/themes/starter/blocks/cta-section/schema.ts +32 -0
  367. package/templates/contents/themes/starter/blocks/cta-section/thumbnail.png +0 -0
  368. package/templates/contents/themes/starter/blocks/features-grid/component.tsx +119 -0
  369. package/templates/contents/themes/starter/blocks/features-grid/config.ts +11 -0
  370. package/templates/contents/themes/starter/blocks/features-grid/fields.ts +97 -0
  371. package/templates/contents/themes/starter/blocks/features-grid/index.ts +6 -0
  372. package/templates/contents/themes/starter/blocks/features-grid/schema.ts +40 -0
  373. package/templates/contents/themes/starter/blocks/features-grid/thumbnail.png +0 -0
  374. package/templates/contents/themes/starter/blocks/hero/component.tsx +105 -0
  375. package/templates/contents/themes/starter/blocks/hero/config.ts +11 -0
  376. package/templates/contents/themes/starter/blocks/hero/fields.ts +60 -0
  377. package/templates/contents/themes/starter/blocks/hero/index.ts +6 -0
  378. package/templates/contents/themes/starter/blocks/hero/schema.ts +33 -0
  379. package/templates/contents/themes/starter/blocks/hero/thumbnail.png +0 -0
  380. package/templates/contents/themes/starter/blocks/testimonials/component.tsx +116 -0
  381. package/templates/contents/themes/starter/blocks/testimonials/config.ts +11 -0
  382. package/templates/contents/themes/starter/blocks/testimonials/fields.ts +105 -0
  383. package/templates/contents/themes/starter/blocks/testimonials/index.ts +6 -0
  384. package/templates/contents/themes/starter/blocks/testimonials/schema.ts +41 -0
  385. package/templates/contents/themes/starter/blocks/testimonials/thumbnail.png +0 -0
  386. package/templates/contents/themes/starter/blocks/text-content/component.tsx +100 -0
  387. package/templates/contents/themes/starter/blocks/text-content/config.ts +11 -0
  388. package/templates/contents/themes/starter/blocks/text-content/fields.ts +88 -0
  389. package/templates/contents/themes/starter/blocks/text-content/index.ts +6 -0
  390. package/templates/contents/themes/starter/blocks/text-content/schema.ts +30 -0
  391. package/templates/contents/themes/starter/blocks/text-content/thumbnail.png +0 -0
  392. package/templates/contents/themes/starter/config/app.config.ts +8 -0
  393. package/templates/contents/themes/starter/entities/tasks/tasks.config.ts +7 -0
  394. package/templates/contents/themes/starter/tests/cypress/e2e/_utils/devtools/access.cy.ts +9 -1
  395. package/templates/contents/themes/starter/tests/cypress/e2e/api/entities/tasks/TaskAPIController.js +2 -2
  396. package/templates/contents/themes/starter/tests/cypress/e2e/uat/entities/tasks/tasks-crud.cy.ts +45 -38
  397. package/templates/contents/themes/starter/tests/cypress/fixtures/entities.json +16 -0
  398. package/templates/contents/themes/starter/tests/cypress/src/components/DevKeyringPOM.ts +3 -1
  399. package/templates/contents/themes/starter/tests/cypress/src/core/BlockEditorBasePOM.ts +3 -3
  400. package/templates/contents/themes/starter/tests/cypress/src/entities/PagesPOM.ts +137 -0
  401. package/templates/contents/themes/starter/tests/cypress/src/entities/index.ts +2 -0
  402. package/templates/contents/themes/starter/tests/cypress/src/index.ts +1 -0
  403. package/templates/contents/themes/starter/tests/cypress/src/session-helpers.ts +42 -11
  404. package/templates/contents/themes/starter/tests/cypress/support/commands.ts +29 -18
  405. package/templates/features/blog/blocks/post-content/component.tsx +5 -1
  406. package/templates/features/blog/blocks/post-content/schema.ts +1 -1
  407. package/templates/features/pages/blocks/hero/component.tsx +3 -0
  408. package/templates/features/pages/blocks/hero/schema.ts +1 -1
  409. package/templates/proxy.ts +138 -53
  410. package/tests/jest/__mocks__/@nextsparkjs/registries/block-registry.client.ts +9 -0
  411. package/tests/jest/__mocks__/@nextsparkjs/registries/block-registry.lazy.ts +5 -0
  412. package/tests/jest/__mocks__/@nextsparkjs/registries/icon-registry.ts +42 -0
  413. package/tests/jest/__mocks__/next-intl-server.js +85 -5
  414. package/tests/jest/__mocks__/next-server.js +32 -0
@@ -0,0 +1,275 @@
1
+ /**
2
+ * Icon Discovery
3
+ *
4
+ * Collects every lucide-react icon name the app can ask for by string at
5
+ * runtime, so the generated icon registry can import exactly those and let
6
+ * the bundler drop the rest of the icon set.
7
+ *
8
+ * Sources are exactly what resolveIcon can be handed, and all of them are
9
+ * source files (never the database):
10
+ * - entity configs (`icon: Users` — an identifier imported from lucide-react)
11
+ * - a theme's app.config (`customSidebarSections[].icon` — a string)
12
+ * - block configs (`icon: 'Grid'` — the block's own icon in the editor)
13
+ *
14
+ * What a block RENDERS is not here: page content names those icons in the page
15
+ * builder, so they live in the database. Those blocks keep resolving through
16
+ * the lucide namespace — deliberately, see themes' features-grid component.
17
+ *
18
+ * @module core/scripts/build/registry/discovery/icons
19
+ */
20
+
21
+ import { readdir, readFile } from 'fs/promises'
22
+ import { existsSync } from 'fs'
23
+ import { createRequire } from 'module'
24
+ import { join, dirname } from 'path'
25
+
26
+ import { verbose, log } from '../../../utils/index.mjs'
27
+
28
+ /**
29
+ * Icons the resolver falls back to when a name doesn't resolve. They are
30
+ * referenced from core components, not from any config, so nothing would
31
+ * discover them.
32
+ */
33
+ const FALLBACK_ICONS = ['Box', 'Circle', 'Folder', 'LayoutGrid']
34
+
35
+ /**
36
+ * Names lucide-react actually exports, read from its own barrel.
37
+ * A name that isn't there would become a broken named import in the
38
+ * generated registry, so unknown names are dropped with a warning instead.
39
+ * @param {object} config
40
+ * @returns {Set<string>|null} null when lucide-react can't be resolved
41
+ */
42
+ async function readLucideExportNames(config) {
43
+ try {
44
+ const requireFromProject = createRequire(join(config.projectRoot, 'package.json'))
45
+ const pkgPath = requireFromProject.resolve('lucide-react/package.json')
46
+ const barrelPath = join(dirname(pkgPath), 'dist/esm/lucide-react.js')
47
+
48
+ if (!existsSync(barrelPath)) {
49
+ return null
50
+ }
51
+
52
+ const barrel = await readFile(barrelPath, 'utf8')
53
+ const names = new Set()
54
+ for (const match of barrel.matchAll(/default as ([A-Za-z_$][\w$]*)/g)) {
55
+ names.add(match[1])
56
+ }
57
+ return names.size > 0 ? names : null
58
+ } catch {
59
+ return null
60
+ }
61
+ }
62
+
63
+ /**
64
+ * Which config files hold icons resolveIcon can be handed. Exported for tests.
65
+ *
66
+ * Separators are normalised first: the generator also runs on Windows, where
67
+ * join() produces '\\' and a '/'-shaped match would silently find nothing —
68
+ * leaving a registry with only its fallbacks, and every entity icon rendering
69
+ * as a Box.
70
+ */
71
+ export function isIconSourcePath(filePath) {
72
+ const normalised = filePath.replace(/\\/g, '/')
73
+ return (
74
+ (normalised.includes('/entities/') && normalised.endsWith('.config.ts')) ||
75
+ (normalised.includes('/blocks/') && normalised.endsWith('/config.ts')) ||
76
+ normalised.endsWith('/config/app.config.ts')
77
+ )
78
+ }
79
+
80
+ /**
81
+ * Map of local name -> exported lucide name for a file's lucide imports.
82
+ * `import { Home as HouseIcon }` means the config's `icon: HouseIcon` is
83
+ * lucide's `Home`.
84
+ */
85
+ function parseLucideImports(content) {
86
+ const imports = new Map()
87
+
88
+ for (const match of content.matchAll(/import\s*(?:type\s*)?\{([^}]*)\}\s*from\s*['"]lucide-react['"]/g)) {
89
+ for (const specifier of match[1].split(',')) {
90
+ const parts = specifier.trim().replace(/^type\s+/, '').split(/\s+as\s+/)
91
+ const exported = parts[0]?.trim()
92
+ const local = (parts[1] || parts[0])?.trim()
93
+ if (exported && local) {
94
+ imports.set(local, exported)
95
+ }
96
+ }
97
+ }
98
+
99
+ return imports
100
+ }
101
+
102
+ /**
103
+ * Icon names referenced by a single config file. Exported for tests.
104
+ */
105
+ export function extractIconNames(content) {
106
+ const lucideImports = parseLucideImports(content)
107
+ const names = []
108
+
109
+ // `icon: Users` or `'icon': Users` — only counts when the identifier came
110
+ // from lucide-react
111
+ for (const match of content.matchAll(/(?:\bicon|['"]icon['"])\s*:\s*([A-Za-z_$][\w$]*)/g)) {
112
+ const exported = lucideImports.get(match[1])
113
+ if (exported) {
114
+ names.push(exported)
115
+ }
116
+ }
117
+
118
+ // `icon: 'Users'` or `icon: 'pie-chart'` — a theme's sidebar sections and
119
+ // block configs use both spellings; the caller folds kebab-case into the
120
+ // lucide export name and validates the result against lucide's export list.
121
+ for (const match of content.matchAll(/(?:\bicon|['"]icon['"])\s*:\s*['"]([A-Za-z][\w$-]*)['"]/g)) {
122
+ names.push(match[1])
123
+ }
124
+
125
+ return names
126
+ }
127
+
128
+ /**
129
+ * Icon references in a config file that this build cannot turn into a name.
130
+ * Exported for tests.
131
+ *
132
+ * Only a direct named import of lucide-react and a string literal reach the
133
+ * registry. Anything else — a namespace access (`I.Users`), an identifier
134
+ * aliased through another module, a component of the project's own — is not
135
+ * resolvable by reading this file, so it never enters the registry and
136
+ * resolveIcon falls back. That is silent, hence the warning.
137
+ */
138
+ export function findUnresolvedIconRefs(content) {
139
+ const lucideImports = parseLucideImports(content)
140
+ const unresolved = []
141
+
142
+ for (const match of content.matchAll(/(?:\bicon|['"]icon['"])\s*:\s*([A-Za-z_$][\w$]*(?:\.[A-Za-z_$][\w$]*)*)/g)) {
143
+ const reference = match[1]
144
+ const isMember = reference.includes('.')
145
+
146
+ if (isMember || !lucideImports.has(reference)) {
147
+ unresolved.push(reference)
148
+ }
149
+ }
150
+
151
+ return unresolved
152
+ }
153
+
154
+ async function collectConfigFiles(dir, matcher, found = []) {
155
+ if (!existsSync(dir)) {
156
+ return found
157
+ }
158
+
159
+ let entries
160
+ try {
161
+ entries = await readdir(dir, { withFileTypes: true })
162
+ } catch {
163
+ return found
164
+ }
165
+
166
+ for (const entry of entries) {
167
+ const entryPath = join(dir, entry.name)
168
+ if (entry.isDirectory()) {
169
+ if (entry.name === 'node_modules' || entry.name.startsWith('.')) continue
170
+ await collectConfigFiles(entryPath, matcher, found)
171
+ } else if (matcher(entryPath)) {
172
+ found.push(entryPath)
173
+ }
174
+ }
175
+
176
+ return found
177
+ }
178
+
179
+ function coreEntitiesDir(config) {
180
+ if (config.isNpmMode) {
181
+ return join(config.projectRoot, 'node_modules/@nextsparkjs/core/src/entities')
182
+ }
183
+ if (config.isMonorepoMode && config.monorepoRoot) {
184
+ return join(config.monorepoRoot, 'packages/core/src/entities')
185
+ }
186
+ return join(config.projectRoot, 'packages/core/src/entities')
187
+ }
188
+
189
+ /**
190
+ * Discover every icon name the runtime can look up by string.
191
+ * @param {Array} blocks - Blocks from block discovery (each with an `icon` name)
192
+ * @param {object} config - Configuration object from getConfig()
193
+ * @returns {Promise<string[]>} Sorted, de-duplicated, validated icon names
194
+ */
195
+ export async function discoverIcons(blocks, config) {
196
+ const candidates = new Set(FALLBACK_ICONS)
197
+
198
+ // Only the configs whose icons reach resolveIcon. Widening this would put
199
+ // icons in the dashboard bundle that nothing can ask for.
200
+ const iconSources = [
201
+ ...(await collectConfigFiles(coreEntitiesDir(config), isIconSourcePath)),
202
+ ...(await collectConfigFiles(config.themesDir, isIconSourcePath)),
203
+ ...(await collectConfigFiles(config.pluginsDir, isIconSourcePath))
204
+ ]
205
+
206
+ const unresolved = []
207
+
208
+ for (const configPath of iconSources) {
209
+ try {
210
+ const content = await readFile(configPath, 'utf8')
211
+ for (const name of extractIconNames(content)) {
212
+ candidates.add(name)
213
+ }
214
+ for (const reference of findUnresolvedIconRefs(content)) {
215
+ unresolved.push({ configPath, reference })
216
+ }
217
+ } catch {
218
+ verbose(`[icons] Could not read ${configPath}`)
219
+ }
220
+ }
221
+
222
+ for (const { configPath, reference } of unresolved) {
223
+ log(
224
+ `[icons] ${configPath.replace(config.projectRoot, '')}: cannot resolve \`icon: ${reference}\` at build time, so it is not in the registry and will render the fallback. Name it with a string, or import it directly from lucide-react.`,
225
+ 'warning'
226
+ )
227
+ }
228
+
229
+ for (const block of blocks || []) {
230
+ if (block.icon) {
231
+ candidates.add(block.icon)
232
+ }
233
+ }
234
+
235
+ const known = await readLucideExportNames(config)
236
+ if (!known) {
237
+ // Every name becomes a named import, so a kebab-case one would emit
238
+ // `import { pie-chart }` and break the build. Without lucide's list there
239
+ // is nothing to fold them against, so they are dropped instead.
240
+ const importable = [...candidates].filter(name => /^[A-Za-z_$][\w$]*$/.test(name))
241
+ log('Could not read lucide-react exports; icon registry will include every importable name found', 'warning')
242
+ return importable.sort()
243
+ }
244
+
245
+ // Configs spell icons both ways: `CheckSquare` in entity configs, `pie-chart`
246
+ // in a theme's sidebar and block configs. The registry is keyed by the export
247
+ // name, so kebab-case names are folded into it rather than dropped.
248
+ const toPascalCase = name =>
249
+ name
250
+ .split(/[-_\s]+/)
251
+ .map(part => part.charAt(0).toUpperCase() + part.slice(1))
252
+ .join('')
253
+
254
+ const valid = []
255
+ const unknown = []
256
+ for (const name of candidates) {
257
+ if (known.has(name)) {
258
+ valid.push(name)
259
+ continue
260
+ }
261
+ const pascalCased = toPascalCase(name)
262
+ if (known.has(pascalCased)) {
263
+ valid.push(pascalCased)
264
+ continue
265
+ }
266
+ unknown.push(name)
267
+ }
268
+
269
+ if (unknown.length > 0) {
270
+ log(`Icons not exported by lucide-react, skipped: ${[...new Set(unknown)].sort().join(', ')}`, 'warning')
271
+ }
272
+
273
+ verbose(`[icons] ${valid.length} icon(s) referenced by name`)
274
+ return [...new Set(valid)].sort()
275
+ }
@@ -13,6 +13,7 @@ export { discoverCoreEntities } from './core-entities.mjs'
13
13
  export { discoverThemes, discoverThemeRouteFiles } from './themes.mjs'
14
14
  export { discoverTemplates, discoverThemeTemplates } from './templates.mjs'
15
15
  export { discoverBlocks } from './blocks.mjs'
16
+ export { discoverIcons } from './icons.mjs'
16
17
  export { discoverMiddlewares } from './middlewares.mjs'
17
18
  export { discoverPermissionsConfig } from './permissions.mjs'
18
19
  export { discoverAuthConfig } from './auth.mjs'
@@ -1,14 +1,61 @@
1
1
  /**
2
2
  * Block Registry Generator
3
3
  *
4
- * Generates block-registry.ts
4
+ * Generates block-registry.ts, block-registry.client.ts and block-registry.lazy.ts
5
+ *
6
+ * The split is what keeps block components out of the browser bundle. A block
7
+ * component is a server component and may import server-only code; the editor,
8
+ * the picker and the devtools viewers are client components that only ever read
9
+ * the configs. Importing both from one module drags every component — and
10
+ * whatever it imports — into the client build, which fails outright when one of
11
+ * them is server-only.
5
12
  *
6
13
  * @module core/scripts/build/registry/generators/block-registry
7
14
  */
8
15
 
9
16
  import { join } from 'path'
17
+ import { toSafeIdentifier } from './identifier.mjs'
10
18
  import { convertCorePath } from '../config.mjs'
11
19
 
20
+ /**
21
+ * The registry entries both halves emit, so the configs cannot drift apart.
22
+ *
23
+ * @param {Array} blocks - Discovered blocks
24
+ * @returns {string} The entries, ready to sit inside the object literal
25
+ */
26
+ function buildRegistryEntries(blocks) {
27
+ return blocks.map(block => {
28
+
29
+ const slugVar = toSafeIdentifier(block.slug)
30
+ const scopeValue = block.scope
31
+ ? `[${block.scope.map(s => `'${s}'`).join(', ')}]`
32
+ : 'undefined'
33
+ const examplesValue = block.hasExamples ? `${slugVar}_examples` : '[]'
34
+ const allowInPatternsValue = block.allowInPatterns !== undefined
35
+ ? block.allowInPatterns.toString()
36
+ : 'undefined'
37
+
38
+ return ` '${block.slug}': {
39
+ slug: '${block.slug}',
40
+ name: '${block.name}',
41
+ description: '${block.description}',
42
+ category: '${block.category}' as BlockCategory,
43
+ icon: '${block.icon}',
44
+ componentPath: '${block.paths.component}',
45
+ schemaPath: '${block.paths.schema}',
46
+ fieldsPath: '${block.paths.fields}',
47
+ thumbnail: ${block.paths.thumbnail ? `'${block.paths.thumbnail}'` : 'undefined'},
48
+ fieldDefinitions: ${slugVar}_fields,
49
+ examples: ${examplesValue},
50
+ scope: ${scopeValue},
51
+ allowInPatterns: ${allowInPatternsValue},
52
+ isCore: false,
53
+ source: 'theme' as const,
54
+ sourceId: '${block.themeName}'
55
+ }`
56
+ }).join(',\n')
57
+ }
58
+
12
59
  /**
13
60
  * Generate the block registry file
14
61
  * @param {Array} blocks - Discovered blocks
@@ -56,11 +103,8 @@ type BlockConfig = {
56
103
  replacedBy?: string
57
104
  }
58
105
 
59
- export const BLOCK_REGISTRY: Record<string, BlockConfig> = {}
60
-
61
- export const BLOCK_CATEGORIES: string[] = []
62
-
63
- export const BLOCK_COMPONENTS: Record<string, React.LazyExoticComponent<React.ComponentType<unknown>>> = {}
106
+ export * from './block-registry.client'
107
+ export { BLOCK_COMPONENTS } from './block-registry.lazy'
64
108
 
65
109
  // eslint-disable-next-line @typescript-eslint/no-explicit-any
66
110
  export const BLOCK_COMPONENTS_SSR: Record<string, React.ComponentType<any>> = {}
@@ -80,7 +124,7 @@ export const BLOCK_METADATA = {
80
124
  const ssrImports = []
81
125
 
82
126
  blocks.forEach(block => {
83
- const slugVar = block.slug.replace(/-/g, '_')
127
+ const slugVar = toSafeIdentifier(block.slug)
84
128
 
85
129
  imports.push(`import { fieldDefinitions as ${slugVar}_fields } from '${block.paths.fields}'`)
86
130
  ssrImports.push(`import { default as ${slugVar}_component } from '${block.paths.component}'`)
@@ -93,35 +137,7 @@ export const BLOCK_METADATA = {
93
137
  const fieldImports = imports.join('\n')
94
138
  const ssrComponentImports = ssrImports.join('\n')
95
139
 
96
- const registryEntries = blocks.map(block => {
97
- const slugVar = block.slug.replace(/-/g, '_')
98
- const scopeValue = block.scope
99
- ? `[${block.scope.map(s => `'${s}'`).join(', ')}]`
100
- : 'undefined'
101
- const examplesValue = block.hasExamples ? `${slugVar}_examples` : '[]'
102
- const allowInPatternsValue = block.allowInPatterns !== undefined
103
- ? block.allowInPatterns.toString()
104
- : 'undefined'
105
-
106
- return ` '${block.slug}': {
107
- slug: '${block.slug}',
108
- name: '${block.name}',
109
- description: '${block.description}',
110
- category: '${block.category}' as BlockCategory,
111
- icon: '${block.icon}',
112
- componentPath: '${block.paths.component}',
113
- schemaPath: '${block.paths.schema}',
114
- fieldsPath: '${block.paths.fields}',
115
- thumbnail: ${block.paths.thumbnail ? `'${block.paths.thumbnail}'` : 'undefined'},
116
- fieldDefinitions: ${slugVar}_fields,
117
- examples: ${examplesValue},
118
- scope: ${scopeValue},
119
- allowInPatterns: ${allowInPatternsValue},
120
- isCore: false,
121
- source: 'theme' as const,
122
- sourceId: '${block.themeName}'
123
- }`
124
- }).join(',\n')
140
+ const registryEntries = buildRegistryEntries(blocks)
125
141
 
126
142
  const categories = [...new Set(blocks.map(b => b.category))]
127
143
 
@@ -139,29 +155,17 @@ export const BLOCK_METADATA = {
139
155
  */
140
156
 
141
157
  import React from 'react'
142
- import type { BlockConfig, BlockCategory } from '${convertCorePath('@/core/types', outputFilePath, config)}'
143
158
 
144
- ${fieldImports}
159
+ // Configs live in the client half so a client component can read them without
160
+ // pulling in a single block component.
161
+ export * from './block-registry.client'
162
+ // Lazy components live in their own module, which a client component can import
163
+ // without the static imports below.
164
+ export { BLOCK_COMPONENTS } from './block-registry.lazy'
145
165
 
146
166
  // Direct component imports for SSR — zero CLS, synchronous hydration
147
167
  ${ssrComponentImports}
148
168
 
149
- export const BLOCK_REGISTRY: Record<string, BlockConfig> = {
150
- ${registryEntries}
151
- }
152
-
153
- export const BLOCK_CATEGORIES: BlockCategory[] = [${categories.map(c => `'${c}'`).join(', ')}]
154
-
155
- /**
156
- * Lazy-loaded block components for client-side rendering (admin UI, block picker).
157
- * Each component is wrapped with React.lazy for code-splitting.
158
- */
159
- export const BLOCK_COMPONENTS: Record<string, React.LazyExoticComponent<React.ComponentType<any>>> = {
160
- ${blocks.map(block => {
161
- return ` '${block.slug}': React.lazy(() => import('${block.paths.component}').then(mod => ({ default: Object.values(mod)[0] as React.ComponentType<any> })))`
162
- }).join(',\n')}
163
- }
164
-
165
169
  /**
166
170
  * Direct-imported block components for SSR rendering.
167
171
  * Static imports ensure synchronous hydration — zero CLS.
@@ -170,7 +174,7 @@ ${blocks.map(block => {
170
174
  // eslint-disable-next-line @typescript-eslint/no-explicit-any
171
175
  export const BLOCK_COMPONENTS_SSR: Record<string, React.ComponentType<any>> = {
172
176
  ${blocks.map(block => {
173
- const slugVar = block.slug.replace(/-/g, '_')
177
+ const slugVar = toSafeIdentifier(block.slug)
174
178
  return ` '${block.slug}': ${slugVar}_component`
175
179
  }).join(',\n')}
176
180
  }
@@ -192,3 +196,147 @@ ${categories.map(cat => {
192
196
  }
193
197
  `
194
198
  }
199
+
200
+ /**
201
+ * Generate the client-safe half: block configs, with no component imports.
202
+ *
203
+ * This is what a client component reads. Keeping it free of component imports
204
+ * is the whole point — a block component is a server component, and pulling one
205
+ * into the client build fails the build when it imports server-only code.
206
+ *
207
+ * @param {Array} blocks - Discovered blocks
208
+ * @param {object} config - Configuration object from getConfig()
209
+ * @returns {string} Generated TypeScript content
210
+ */
211
+ export function generateBlockRegistryClient(blocks, config) {
212
+ const outputFilePath = join(config.outputDir, 'block-registry.client.ts')
213
+ const header = `/**
214
+ * Auto-generated Block Registry (client-safe)
215
+ *
216
+ * Generated at: ${new Date().toISOString()}
217
+ * Blocks discovered: ${blocks.length}
218
+ *
219
+ * Block configs only: no component imports, so a client component can read the
220
+ * registry without the block components reaching the browser bundle.
221
+ *
222
+ * DO NOT EDIT - This file is auto-generated by scripts/build-registry.mjs
223
+ */
224
+ `
225
+
226
+ if (blocks.length === 0) {
227
+ return `${header}
228
+ // Inline type definition for empty registry (avoids import issues in npm mode)
229
+ type BlockConfig = {
230
+ slug: string
231
+ name: string
232
+ description?: string
233
+ category: string
234
+ icon?: string
235
+ componentPath?: string
236
+ schemaPath?: string
237
+ fieldsPath?: string
238
+ thumbnail?: string
239
+ fieldDefinitions: unknown[]
240
+ examples: unknown[]
241
+ scope?: string[]
242
+ allowInPatterns?: boolean
243
+ schemaType?: string
244
+ tags?: string[]
245
+ isCore?: boolean
246
+ source?: string
247
+ sourceId?: string
248
+ version?: string
249
+ deprecated?: boolean
250
+ replacedBy?: string
251
+ }
252
+
253
+ export const BLOCK_REGISTRY: Record<string, BlockConfig> = {}
254
+
255
+ export const BLOCK_CATEGORIES: string[] = []
256
+
257
+ export const BLOCK_METADATA = {
258
+ totalBlocks: 0,
259
+ categories: [],
260
+ blocksByCategory: {},
261
+ generatedAt: '${new Date().toISOString()}',
262
+ blocks: []
263
+ }
264
+ `
265
+ }
266
+
267
+ const imports = []
268
+ blocks.forEach(block => {
269
+ const slugVar = toSafeIdentifier(block.slug)
270
+ imports.push(`import { fieldDefinitions as ${slugVar}_fields } from '${block.paths.fields}'`)
271
+ if (block.hasExamples) {
272
+ imports.push(`import { examples as ${slugVar}_examples } from '${block.paths.examples}'`)
273
+ }
274
+ })
275
+
276
+ const registryEntries = buildRegistryEntries(blocks)
277
+ const categories = [...new Set(blocks.map(b => b.category))]
278
+
279
+ return `${header}
280
+ import type { BlockConfig, BlockCategory } from '${convertCorePath('@/core/types', outputFilePath, config)}'
281
+
282
+ ${imports.join('\n')}
283
+
284
+ export const BLOCK_REGISTRY: Record<string, BlockConfig> = {
285
+ ${registryEntries}
286
+ }
287
+
288
+ export const BLOCK_CATEGORIES: BlockCategory[] = [${categories.map(c => `'${c}'`).join(', ')}]
289
+
290
+ /**
291
+ * Block registry metadata
292
+ */
293
+ export const BLOCK_METADATA = {
294
+ totalBlocks: ${blocks.length},
295
+ categories: [${categories.map(c => `'${c}'`).join(', ')}],
296
+ blocksByCategory: {
297
+ ${categories.map(cat => ` '${cat}': ${blocks.filter(b => b.category === cat).length}`).join(',\n')}
298
+ },
299
+ generatedAt: '${new Date().toISOString()}',
300
+ blocks: [${blocks.map(b => `'${b.slug}'`).join(', ')}]
301
+ }
302
+ `
303
+ }
304
+
305
+ /**
306
+ * Generate the lazy half: every block component behind React.lazy, and no
307
+ * static import.
308
+ *
309
+ * Client components that render blocks (the editor canvas, pattern previews,
310
+ * devtools) read this, so each block stays in a chunk of its own and loads
311
+ * when it is shown. Reaching the components through block-registry.ts instead
312
+ * carries its static SSR imports along, and with them every block and all a
313
+ * block imports, into that client component's bundle.
314
+ *
315
+ * @param {Array} blocks - Discovered blocks
316
+ * @returns {string} Generated TypeScript content
317
+ */
318
+ export function generateBlockRegistryLazy(blocks) {
319
+ const entries = blocks.map(block =>
320
+ ` '${block.slug}': React.lazy(() => import('${block.paths.component}').then(mod => ({ default: Object.values(mod)[0] as React.ComponentType<any> })))`
321
+ ).join(',\n')
322
+
323
+ return `/**
324
+ * Auto-generated Block Registry (lazy components)
325
+ *
326
+ * Generated at: ${new Date().toISOString()}
327
+ * Blocks discovered: ${blocks.length}
328
+ *
329
+ * Block components behind React.lazy and nothing imported statically, so a
330
+ * client component can render blocks while each one stays in its own chunk.
331
+ *
332
+ * DO NOT EDIT - This file is auto-generated by scripts/build-registry.mjs
333
+ */
334
+
335
+ import React from 'react'
336
+
337
+ // eslint-disable-next-line @typescript-eslint/no-explicit-any
338
+ export const BLOCK_COMPONENTS: Record<string, React.LazyExoticComponent<React.ComponentType<any>>> = {
339
+ ${entries}
340
+ }
341
+ `
342
+ }
@@ -58,8 +58,15 @@ function stringifyWithIndent(obj, baseIndent = 2) {
58
58
  * @returns {Object} Map of slug -> { slug, name }
59
59
  */
60
60
  function loadBlockSlugs(registryDir) {
61
- const registryPath = join(registryDir, 'block-registry.ts')
62
- if (!existsSync(registryPath)) {
61
+ // The entries live in the client half; the server one re-exports them. The
62
+ // fallback is for a project generated before the split, whose server file
63
+ // still holds them inline.
64
+ const registryPath = [
65
+ join(registryDir, 'block-registry.client.ts'),
66
+ join(registryDir, 'block-registry.ts'),
67
+ ].find(existsSync)
68
+
69
+ if (!registryPath) {
63
70
  return {}
64
71
  }
65
72
 
@@ -0,0 +1,56 @@
1
+ /**
2
+ * Icon Registry Generator
3
+ *
4
+ * Generates icon-registry.ts: a name -> component map holding only the
5
+ * lucide-react icons the app can look up by string.
6
+ *
7
+ * Named imports are what makes this work. `import * as Icons from
8
+ * 'lucide-react'` forces the bundler to keep the whole icon set — ~500 KB
9
+ * that ships to every route, including public ones that render none of it —
10
+ * because it can't prove which members are used. The set of names is known
11
+ * at build time (see discovery/icons.mjs), so it is spelled out here.
12
+ *
13
+ * @module core/scripts/build/registry/generators/icon-registry
14
+ */
15
+
16
+ /**
17
+ * Generate the icon registry file
18
+ * @param {string[]} iconNames - Icon names discovered by discoverIcons()
19
+ * @param {object} config - Configuration object from getConfig()
20
+ * @returns {string} Generated TypeScript content
21
+ */
22
+ export function generateIconRegistry(iconNames, config) {
23
+ const names = [...new Set(iconNames)].sort()
24
+ const importList = names.map(name => ` ${name}`).join(',\n')
25
+ const entryList = names.map(name => ` ${name}`).join(',\n')
26
+
27
+ return `/**
28
+ * Auto-generated Icon Registry
29
+ *
30
+ * Generated at: ${new Date().toISOString()}
31
+ * Icons referenced by name: ${names.length}
32
+ *
33
+ * DO NOT EDIT - This file is auto-generated by scripts/build-registry.mjs
34
+ */
35
+
36
+ import type { LucideIcon } from 'lucide-react'
37
+ import {
38
+ ${importList}
39
+ } from 'lucide-react'
40
+
41
+ /**
42
+ * Every icon the app can resolve from a configured string name.
43
+ * Anything outside this map falls back to the caller's default icon.
44
+ */
45
+ export const ICON_REGISTRY: Record<string, LucideIcon> = {
46
+ ${entryList}
47
+ }
48
+
49
+ export type IconName = keyof typeof ICON_REGISTRY
50
+
51
+ export const ICON_METADATA = {
52
+ totalIcons: ${names.length},
53
+ generatedAt: '${new Date().toISOString()}'
54
+ }
55
+ `
56
+ }