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

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 (456) hide show
  1. package/dist/components/api/keys/CreateApiKeyDialog.d.ts.map +1 -1
  2. package/dist/components/api/keys/CreateApiKeyDialog.js +2 -1
  3. package/dist/components/app/layouts/PublicFooter.d.ts.map +1 -1
  4. package/dist/components/app/layouts/PublicFooter.js +2 -23
  5. package/dist/components/app/misc/ThemeToggle.d.ts.map +1 -1
  6. package/dist/components/app/misc/ThemeToggle.js +2 -1
  7. package/dist/components/auth/forms/LoginForm.js +1 -1
  8. package/dist/components/auth/forms/SignupForm.d.ts.map +1 -1
  9. package/dist/components/auth/forms/SignupForm.js +2 -1
  10. package/dist/components/auth/pages/AuthErrorPage.d.ts.map +1 -1
  11. package/dist/components/auth/pages/AuthErrorPage.js +2 -1
  12. package/dist/components/billing/ManageBillingButton.d.ts.map +1 -1
  13. package/dist/components/billing/ManageBillingButton.js +2 -1
  14. package/dist/components/dashboard/block-editor/array-field.d.ts.map +1 -1
  15. package/dist/components/dashboard/block-editor/array-field.js +2 -1
  16. package/dist/components/dashboard/block-editor/block-picker.d.ts.map +1 -1
  17. package/dist/components/dashboard/block-editor/block-picker.js +2 -1
  18. package/dist/components/dashboard/block-editor/builder-editor-view.d.ts.map +1 -1
  19. package/dist/components/dashboard/block-editor/builder-editor-view.js +3 -2
  20. package/dist/components/dashboard/block-editor/config-panel.d.ts.map +1 -1
  21. package/dist/components/dashboard/block-editor/config-panel.js +3 -2
  22. package/dist/components/dashboard/block-editor/dynamic-form.d.ts.map +1 -1
  23. package/dist/components/dashboard/block-editor/dynamic-form.js +2 -1
  24. package/dist/components/dashboard/block-editor/entity-fields-sidebar.d.ts.map +1 -1
  25. package/dist/components/dashboard/block-editor/entity-fields-sidebar.js +3 -2
  26. package/dist/components/dashboard/block-editor/pattern-reference-preview.d.ts.map +1 -1
  27. package/dist/components/dashboard/block-editor/pattern-reference-preview.js +2 -1
  28. package/dist/components/dashboard/layouts/TopNavbar.d.ts.map +1 -1
  29. package/dist/components/dashboard/layouts/TopNavbar.js +4 -3
  30. package/dist/components/dashboard/misc/SearchDropdown.d.ts.map +1 -1
  31. package/dist/components/dashboard/misc/SearchDropdown.js +2 -1
  32. package/dist/components/dashboard/mobile/MobileTopBar.d.ts.map +1 -1
  33. package/dist/components/dashboard/mobile/MobileTopBar.js +2 -1
  34. package/dist/components/devtools/BlockDetailViewer.d.ts.map +1 -1
  35. package/dist/components/devtools/BlockDetailViewer.js +2 -1
  36. package/dist/components/devtools/BlocksViewer.d.ts.map +1 -1
  37. package/dist/components/devtools/BlocksViewer.js +2 -1
  38. package/dist/components/devtools/ConfigViewer.d.ts.map +1 -1
  39. package/dist/components/devtools/ConfigViewer.js +3 -2
  40. package/dist/components/devtools/TestCasesViewer.d.ts.map +1 -1
  41. package/dist/components/devtools/TestCasesViewer.js +3 -2
  42. package/dist/components/devtools/TestCoverageDashboard.d.ts.map +1 -1
  43. package/dist/components/devtools/TestCoverageDashboard.js +2 -1
  44. package/dist/components/devtools/api-explorer/ApiDocsModal.d.ts.map +1 -1
  45. package/dist/components/devtools/api-explorer/ApiDocsModal.js +0 -0
  46. package/dist/components/devtools/api-explorer/ApiExplorer.d.ts.map +1 -1
  47. package/dist/components/devtools/api-explorer/ApiExplorer.js +2 -1
  48. package/dist/components/devtools/scheduled-actions/actions-table.d.ts.map +1 -1
  49. package/dist/components/devtools/scheduled-actions/actions-table.js +3 -2
  50. package/dist/components/docs/docs-sidebar.d.ts.map +1 -1
  51. package/dist/components/docs/docs-sidebar.js +3 -2
  52. package/dist/components/entities/EntityFieldRenderer.d.ts.map +1 -1
  53. package/dist/components/entities/EntityFieldRenderer.js +2 -1
  54. package/dist/components/entities/EntityList.d.ts.map +1 -1
  55. package/dist/components/entities/EntityList.js +2 -1
  56. package/dist/components/entities/EntityTable.d.ts.map +1 -1
  57. package/dist/components/entities/EntityTable.js +3 -2
  58. package/dist/components/entities/ErrorBoundary.d.ts.map +1 -1
  59. package/dist/components/entities/ErrorBoundary.js +2 -1
  60. package/dist/components/entities/wrappers/EntityDetailWrapper.d.ts.map +1 -1
  61. package/dist/components/entities/wrappers/EntityDetailWrapper.js +3 -2
  62. package/dist/components/media/MediaCard.d.ts.map +1 -1
  63. package/dist/components/media/MediaCard.js +2 -1
  64. package/dist/components/media/MediaDetailPanel.d.ts.map +1 -1
  65. package/dist/components/media/MediaDetailPanel.js +2 -1
  66. package/dist/components/media/MediaList.d.ts.map +1 -1
  67. package/dist/components/media/MediaList.js +2 -1
  68. package/dist/components/media/MediaSelector.d.ts.map +1 -1
  69. package/dist/components/media/MediaSelector.js +2 -1
  70. package/dist/components/media/MediaUploadZone.d.ts.map +1 -1
  71. package/dist/components/media/MediaUploadZone.js +2 -1
  72. package/dist/components/public/entities/PublicEntityDetail.d.ts.map +1 -1
  73. package/dist/components/public/entities/PublicEntityDetail.js +2 -1
  74. package/dist/components/teams/TeamPendingInvitations.d.ts.map +1 -1
  75. package/dist/components/teams/TeamPendingInvitations.js +3 -2
  76. package/dist/components/ui/accordion.js +1 -1
  77. package/dist/components/ui/audio-upload.d.ts.map +1 -1
  78. package/dist/components/ui/audio-upload.js +2 -1
  79. package/dist/components/ui/avatar.d.ts +17 -1
  80. package/dist/components/ui/avatar.d.ts.map +1 -1
  81. package/dist/components/ui/avatar.js +29 -2
  82. package/dist/components/ui/badge.js +1 -1
  83. package/dist/components/ui/breadcrumb.d.ts +13 -0
  84. package/dist/components/ui/breadcrumb.d.ts.map +1 -1
  85. package/dist/components/ui/breadcrumb.js +23 -10
  86. package/dist/components/ui/button.js +1 -1
  87. package/dist/components/ui/card.js +1 -1
  88. package/dist/components/ui/checkbox.js +1 -1
  89. package/dist/components/ui/file-upload.d.ts.map +1 -1
  90. package/dist/components/ui/file-upload.js +2 -1
  91. package/dist/components/ui/image-upload.d.ts.map +1 -1
  92. package/dist/components/ui/image-upload.js +4 -3
  93. package/dist/components/ui/input.js +1 -1
  94. package/dist/components/ui/label.js +1 -1
  95. package/dist/components/ui/optimized-image.d.ts.map +1 -1
  96. package/dist/components/ui/optimized-image.js +5 -0
  97. package/dist/components/ui/pagination.d.ts +1 -1
  98. package/dist/components/ui/pagination.d.ts.map +1 -1
  99. package/dist/components/ui/pagination.js +3 -0
  100. package/dist/components/ui/progress.js +1 -1
  101. package/dist/components/ui/relation-display.d.ts.map +1 -1
  102. package/dist/components/ui/relation-display.js +3 -2
  103. package/dist/components/ui/separator.js +1 -1
  104. package/dist/components/ui/simple-relation-select.d.ts.map +1 -1
  105. package/dist/components/ui/simple-relation-select.js +7 -6
  106. package/dist/components/ui/skeleton.js +1 -1
  107. package/dist/components/ui/slider.js +1 -1
  108. package/dist/components/ui/switch.js +1 -1
  109. package/dist/components/ui/tabs.js +1 -1
  110. package/dist/components/ui/textarea.js +1 -1
  111. package/dist/components/ui/user-display.d.ts.map +1 -1
  112. package/dist/components/ui/user-display.js +2 -1
  113. package/dist/components/ui/user-select.d.ts.map +1 -1
  114. package/dist/components/ui/user-select.js +2 -1
  115. package/dist/components/ui/video-upload.d.ts.map +1 -1
  116. package/dist/components/ui/video-upload.js +3 -2
  117. package/dist/components/users/tables/UsersTable.d.ts.map +1 -1
  118. package/dist/components/users/tables/UsersTable.js +3 -2
  119. package/dist/contexts/SubscriptionContext.d.ts.map +1 -1
  120. package/dist/contexts/SubscriptionContext.js +4 -3
  121. package/dist/contexts/TeamContext.d.ts.map +1 -1
  122. package/dist/contexts/TeamContext.js +3 -2
  123. package/dist/hooks/useAccountPreferencesSync.d.ts.map +1 -1
  124. package/dist/hooks/useAccountPreferencesSync.js +2 -1
  125. package/dist/hooks/useApiExplorerNavigation.d.ts.map +1 -1
  126. package/dist/hooks/useApiExplorerNavigation.js +2 -1
  127. package/dist/hooks/useAuth.d.ts.map +1 -1
  128. package/dist/hooks/useAuth.js +3 -2
  129. package/dist/hooks/useEnsureUserMetadata.d.ts.map +1 -1
  130. package/dist/hooks/useEnsureUserMetadata.js +3 -2
  131. package/dist/hooks/useEntityWithMeta.d.ts.map +1 -1
  132. package/dist/hooks/useEntityWithMeta.js +3 -2
  133. package/dist/hooks/useInvoice.d.ts.map +1 -1
  134. package/dist/hooks/useInvoice.js +2 -1
  135. package/dist/hooks/useInvoices.d.ts.map +1 -1
  136. package/dist/hooks/useInvoices.js +2 -1
  137. package/dist/hooks/useMedia.d.ts.map +1 -1
  138. package/dist/hooks/useMedia.js +10 -9
  139. package/dist/hooks/useMediaUpload.d.ts.map +1 -1
  140. package/dist/hooks/useMediaUpload.js +2 -1
  141. package/dist/hooks/useMembership.d.ts.map +1 -1
  142. package/dist/hooks/useMembership.js +3 -2
  143. package/dist/hooks/usePatternUsages.d.ts.map +1 -1
  144. package/dist/hooks/usePatternUsages.js +3 -2
  145. package/dist/hooks/usePrefetch.d.ts.map +1 -1
  146. package/dist/hooks/usePrefetch.js +3 -2
  147. package/dist/hooks/useQuota.d.ts.map +1 -1
  148. package/dist/hooks/useQuota.js +2 -1
  149. package/dist/hooks/useTeamInvitations.d.ts.map +1 -1
  150. package/dist/hooks/useTeamInvitations.js +4 -3
  151. package/dist/hooks/useTeamMembers.d.ts.map +1 -1
  152. package/dist/hooks/useTeamMembers.js +5 -4
  153. package/dist/hooks/useTeamUpdate.d.ts.map +1 -1
  154. package/dist/hooks/useTeamUpdate.js +2 -1
  155. package/dist/hooks/useUserProfile.d.ts.map +1 -1
  156. package/dist/hooks/useUserProfile.js +3 -2
  157. package/dist/hooks/useUserSettings.d.ts.map +1 -1
  158. package/dist/hooks/useUserSettings.js +3 -2
  159. package/dist/lib/actions/entity.actions.d.ts.map +1 -1
  160. package/dist/lib/actions/entity.actions.js +2 -1
  161. package/dist/lib/api/entities.d.ts.map +1 -1
  162. package/dist/lib/api/entities.js +4 -3
  163. package/dist/lib/auth/verify-email-link.d.ts +10 -0
  164. package/dist/lib/auth/verify-email-link.d.ts.map +1 -0
  165. package/dist/lib/auth/verify-email-link.js +14 -0
  166. package/dist/lib/auth-client.d.ts +2 -0
  167. package/dist/lib/auth-client.d.ts.map +1 -1
  168. package/dist/lib/auth-client.js +4 -0
  169. package/dist/lib/auth.d.ts.map +1 -1
  170. package/dist/lib/auth.js +10 -2
  171. package/dist/lib/base-path.d.ts +85 -0
  172. package/dist/lib/base-path.d.ts.map +1 -0
  173. package/dist/lib/base-path.js +79 -0
  174. package/dist/lib/billing/schema.d.ts +1 -1
  175. package/dist/lib/billing/schema.d.ts.map +1 -1
  176. package/dist/lib/billing/schema.js +1 -1
  177. package/dist/lib/blocks/sanitize-html.d.ts.map +1 -1
  178. package/dist/lib/blocks/sanitize-html.js +18 -2
  179. package/dist/lib/cache/revalidate-tag.d.ts +14 -0
  180. package/dist/lib/cache/revalidate-tag.d.ts.map +1 -0
  181. package/dist/lib/cache/revalidate-tag.js +8 -0
  182. package/dist/lib/config/dashboard-schema.d.ts +1 -1
  183. package/dist/lib/config/dashboard-schema.d.ts.map +1 -1
  184. package/dist/lib/config/dashboard-schema.js +1 -1
  185. package/dist/lib/config/types.d.ts +36 -16
  186. package/dist/lib/config/types.d.ts.map +1 -1
  187. package/dist/lib/docs/access.d.ts +28 -0
  188. package/dist/lib/docs/access.d.ts.map +1 -0
  189. package/dist/lib/docs/access.js +16 -0
  190. package/dist/lib/docs/parser.d.ts.map +1 -1
  191. package/dist/lib/docs/parser.js +2 -1
  192. package/dist/lib/docs/remark-doc-links.d.ts +14 -0
  193. package/dist/lib/docs/remark-doc-links.d.ts.map +1 -0
  194. package/dist/lib/docs/remark-doc-links.js +38 -0
  195. package/dist/lib/entities/api-generator.d.ts +1 -1
  196. package/dist/lib/entities/api-generator.d.ts.map +1 -1
  197. package/dist/lib/entities/api-generator.js +1 -1
  198. package/dist/lib/entities/schema-generator.d.ts +1 -1
  199. package/dist/lib/entities/schema-generator.d.ts.map +1 -1
  200. package/dist/lib/entities/schema-generator.js +1 -1
  201. package/dist/lib/mcp/engine.js +1 -1
  202. package/dist/lib/mcp/schema-builder.d.ts +1 -1
  203. package/dist/lib/mcp/schema-builder.d.ts.map +1 -1
  204. package/dist/lib/mcp/schema-builder.js +1 -1
  205. package/dist/lib/mcp/tool-generator.js +1 -1
  206. package/dist/lib/mcp/types.d.ts +1 -1
  207. package/dist/lib/mcp/types.d.ts.map +1 -1
  208. package/dist/lib/media/schemas.d.ts +1 -1
  209. package/dist/lib/media/schemas.d.ts.map +1 -1
  210. package/dist/lib/media/schemas.js +1 -1
  211. package/dist/lib/middleware/index.d.ts +5 -1
  212. package/dist/lib/middleware/index.d.ts.map +1 -1
  213. package/dist/lib/middleware/index.js +3 -2
  214. package/dist/lib/services/meta.service.d.ts +60 -0
  215. package/dist/lib/services/meta.service.d.ts.map +1 -1
  216. package/dist/lib/services/meta.service.js +132 -12
  217. package/dist/lib/services/middleware.service.d.ts.map +1 -1
  218. package/dist/lib/services/middleware.service.js +3 -2
  219. package/dist/lib/teams/schema.d.ts +1 -1
  220. package/dist/lib/teams/schema.d.ts.map +1 -1
  221. package/dist/lib/teams/schema.js +1 -1
  222. package/dist/lib/teams/teams.api.d.ts.map +1 -1
  223. package/dist/lib/teams/teams.api.js +4 -3
  224. package/dist/lib/user-data-client.d.ts.map +1 -1
  225. package/dist/lib/user-data-client.js +3 -2
  226. package/dist/lib/validation/invoices.d.ts +1 -1
  227. package/dist/lib/validation/invoices.d.ts.map +1 -1
  228. package/dist/lib/validation/invoices.js +1 -1
  229. package/dist/lib/validation-schemas.d.ts +1 -1
  230. package/dist/lib/validation-schemas.d.ts.map +1 -1
  231. package/dist/lib/validation-schemas.js +1 -1
  232. package/dist/lib/validation.d.ts +1 -1
  233. package/dist/lib/validation.d.ts.map +1 -1
  234. package/dist/lib/validation.js +1 -1
  235. package/dist/messages/de/footer.json +0 -1
  236. package/dist/messages/de/index.d.ts +0 -1
  237. package/dist/messages/de/index.d.ts.map +1 -1
  238. package/dist/messages/en/footer.json +0 -1
  239. package/dist/messages/en/index.d.ts +0 -1
  240. package/dist/messages/en/index.d.ts.map +1 -1
  241. package/dist/messages/es/footer.json +0 -1
  242. package/dist/messages/es/index.d.ts +0 -1
  243. package/dist/messages/es/index.d.ts.map +1 -1
  244. package/dist/messages/fr/footer.json +0 -1
  245. package/dist/messages/fr/index.d.ts +0 -1
  246. package/dist/messages/fr/index.d.ts.map +1 -1
  247. package/dist/messages/it/footer.json +0 -1
  248. package/dist/messages/it/index.d.ts +0 -1
  249. package/dist/messages/it/index.d.ts.map +1 -1
  250. package/dist/messages/pt/footer.json +0 -1
  251. package/dist/messages/pt/index.d.ts +0 -1
  252. package/dist/messages/pt/index.d.ts.map +1 -1
  253. package/dist/templates/app/(auth)/accept-invite/[token]/page.tsx +4 -3
  254. package/dist/templates/app/(auth)/verify-email/page.tsx +4 -3
  255. package/dist/templates/app/(public)/docs/[section]/[page]/page.tsx +11 -0
  256. package/dist/templates/app/api/auth/[...all]/route.ts +15 -14
  257. package/dist/templates/app/api/internal/user-metadata/route.ts +6 -8
  258. package/dist/templates/app/api/user/plan-flags/route.ts +1 -1
  259. package/dist/templates/app/api/user/profile/route.ts +12 -17
  260. package/dist/templates/app/api/v1/api-keys/route.ts +1 -1
  261. package/dist/templates/app/api/v1/auth/signup-with-invite/route.ts +2 -1
  262. package/dist/templates/app/api/v1/billing/cancel/route.ts +1 -1
  263. package/dist/templates/app/api/v1/billing/change-plan/route.ts +1 -1
  264. package/dist/templates/app/api/v1/billing/check-action/route.ts +1 -1
  265. package/dist/templates/app/api/v1/billing/checkout/route.ts +1 -1
  266. package/dist/templates/app/api/v1/blocks/validate/route.ts +1 -1
  267. package/dist/templates/app/api/v1/media/[id]/tags/route.ts +1 -1
  268. package/dist/templates/app/api/v1/post-categories/[id]/route.ts +1 -1
  269. package/dist/templates/app/api/v1/post-categories/route.ts +1 -1
  270. package/dist/templates/app/api/v1/teams/[teamId]/members/route.ts +2 -1
  271. package/dist/templates/app/api/v1/teams/[teamId]/subscription/route.ts +3 -5
  272. package/dist/templates/app/api/v1/teams/docs.md +4 -9
  273. package/dist/templates/app/api/v1/teams/switch/route.ts +1 -1
  274. package/dist/templates/app/api/v1/users/[id]/meta/[key]/route.ts +1 -1
  275. package/dist/templates/app/api/v1/users/[id]/route.ts +1 -1
  276. package/dist/templates/app/api/v1/users/route.ts +1 -1
  277. package/dist/templates/app/dashboard/(main)/media/page.tsx +3 -1
  278. package/dist/templates/app/dashboard/settings/api-keys/page.tsx +5 -4
  279. package/dist/templates/app/dashboard/settings/profile/page.tsx +3 -2
  280. package/dist/templates/app/devtools/scheduled-actions/page.tsx +2 -1
  281. package/dist/templates/app/superadmin/docs/[section]/[page]/page.tsx +16 -0
  282. package/dist/templates/app/superadmin/subscriptions/page.tsx +3 -2
  283. package/dist/templates/app/superadmin/teams/[teamId]/page.tsx +5 -4
  284. package/dist/templates/app/superadmin/users/[userId]/page.tsx +2 -1
  285. package/dist/templates/app/superadmin/users/page.tsx +2 -1
  286. package/dist/templates/blocks/cta-section/component.tsx +3 -2
  287. package/dist/templates/blocks/cta-section/schema.ts +1 -1
  288. package/dist/templates/blocks/features-grid/component.tsx +2 -1
  289. package/dist/templates/blocks/features-grid/schema.ts +1 -1
  290. package/dist/templates/blocks/hero/component.tsx +3 -2
  291. package/dist/templates/blocks/hero/schema.ts +1 -1
  292. package/dist/templates/blocks/testimonials/component.tsx +2 -1
  293. package/dist/templates/blocks/testimonials/schema.ts +1 -1
  294. package/dist/templates/blocks/text-content/component.tsx +2 -1
  295. package/dist/templates/blocks/text-content/schema.ts +1 -1
  296. package/dist/templates/ci-workflows/README.md +1 -1
  297. package/dist/templates/ci-workflows/github/cypress-regression.yml +1 -1
  298. package/dist/templates/ci-workflows/github/cypress-smoke.yml +1 -1
  299. package/dist/templates/contents/plugins/starter/plugin/api/example/route.ts.template +1 -1
  300. package/dist/templates/contents/themes/starter/blocks/cta-section/component.tsx +3 -2
  301. package/dist/templates/contents/themes/starter/blocks/cta-section/schema.ts +1 -1
  302. package/dist/templates/contents/themes/starter/blocks/features-grid/component.tsx +2 -1
  303. package/dist/templates/contents/themes/starter/blocks/features-grid/schema.ts +1 -1
  304. package/dist/templates/contents/themes/starter/blocks/hero/component.tsx +3 -2
  305. package/dist/templates/contents/themes/starter/blocks/hero/schema.ts +1 -1
  306. package/dist/templates/contents/themes/starter/blocks/testimonials/component.tsx +3 -2
  307. package/dist/templates/contents/themes/starter/blocks/testimonials/schema.ts +1 -1
  308. package/dist/templates/contents/themes/starter/blocks/text-content/component.tsx +2 -1
  309. package/dist/templates/contents/themes/starter/blocks/text-content/schema.ts +1 -1
  310. package/dist/templates/contents/themes/starter/entities/posts/components/post-header.tsx +2 -1
  311. package/dist/templates/contents/themes/starter/lib/hooks/useAnalyticsData.ts +2 -1
  312. package/dist/templates/contents/themes/starter/lib/hooks/useTaskStats.ts +2 -1
  313. package/dist/templates/eslint.config.mjs +133 -9
  314. package/dist/templates/features/blog/blocks/post-content/component.tsx +2 -1
  315. package/dist/templates/features/blog/blocks/post-content/schema.ts +1 -1
  316. package/dist/templates/features/blog/entities/posts/components/post-header.tsx +2 -1
  317. package/dist/templates/features/pages/blocks/hero/component.tsx +3 -2
  318. package/dist/templates/features/pages/blocks/hero/schema.ts +1 -1
  319. package/dist/templates/next.config.mjs +50 -29
  320. package/dist/templates/proxy.ts +100 -21
  321. package/dist/templates/public/sw.js +2 -2
  322. package/dist/templates/scripts/cy-run-prod.cjs +5 -5
  323. package/dist/types/blocks.d.ts +1 -1
  324. package/dist/types/blocks.d.ts.map +1 -1
  325. package/dist/types/blocks.js +1 -1
  326. package/package.json +9 -5
  327. package/scripts/build/docs-registry.mjs +7 -7
  328. package/scripts/build/registry/__tests__/billing-registry.test.mjs +43 -0
  329. package/scripts/build/registry/__tests__/build-output.test.mjs +209 -0
  330. package/scripts/build/registry/__tests__/copy-race-child.mjs +14 -0
  331. package/scripts/build/registry/__tests__/direct-fs-writes.mjs +203 -0
  332. package/scripts/build/registry/__tests__/globals-css-tag.test.mjs +66 -0
  333. package/scripts/build/registry/__tests__/icons-discovery.test.mjs +530 -29
  334. package/scripts/build/registry/__tests__/page-generator.test.mjs +211 -37
  335. package/scripts/build/registry/__tests__/published-signatures.test.mjs +239 -0
  336. package/scripts/build/registry/__tests__/route-file-rule.test.mjs +170 -0
  337. package/scripts/build/registry/__tests__/safe-fs.test.mjs +333 -0
  338. package/scripts/build/registry/__tests__/template-analysis.test.mjs +142 -0
  339. package/scripts/build/registry/__tests__/template-registry.test.mjs +279 -9
  340. package/scripts/build/registry/__tests__/templates-plan.test.mjs +128 -0
  341. package/scripts/build/registry/__tests__/templates-tree.test.mjs +367 -0
  342. package/scripts/build/registry/__tests__/write-places.test.mjs +425 -0
  343. package/scripts/build/registry/config.mjs +6 -65
  344. package/scripts/build/registry/discovery/icons.mjs +498 -43
  345. package/scripts/build/registry/discovery/templates.mjs +1 -1
  346. package/scripts/build/registry/generators/billing-registry.mjs +1 -1
  347. package/scripts/build/registry/generators/docs-registry.mjs +3 -4
  348. package/scripts/build/registry/generators/feature-registry.mjs +1 -1
  349. package/scripts/build/registry/generators/permissions-registry.mjs +2 -2
  350. package/scripts/build/registry/generators/template-registry.mjs +65 -49
  351. package/scripts/build/registry/index.mjs +1 -2
  352. package/scripts/build/registry/post-build/index.mjs +1 -1
  353. package/scripts/build/registry/post-build/own-gitignores.mjs +220 -0
  354. package/scripts/build/registry/post-build/page-generator.mjs +512 -268
  355. package/scripts/build/registry/post-build/route-cleanup.mjs +16 -13
  356. package/scripts/build/registry/post-build/test-fixtures.mjs +26 -8
  357. package/scripts/build/registry/project-mode.mjs +76 -0
  358. package/scripts/build/registry/shared/typescript-compiler.mjs +77 -0
  359. package/scripts/build/registry/validate-env.test.mjs +12 -8
  360. package/scripts/build/registry/watch.mjs +3 -53
  361. package/scripts/build/registry/write-places.mjs +228 -0
  362. package/scripts/build/registry.mjs +53 -30
  363. package/scripts/build/safe-fs.mjs +279 -0
  364. package/scripts/build/templates-plan.mjs +44 -0
  365. package/scripts/build/theme.mjs +46 -42
  366. package/scripts/build/update-tsconfig.mjs +16 -15
  367. package/scripts/db/cluster-changes.mjs +205 -0
  368. package/scripts/db/connection-time-limits.mjs +53 -0
  369. package/scripts/db/inspect-server.mjs +108 -0
  370. package/scripts/db/migration-time-limit.mjs +695 -0
  371. package/scripts/db/run-migrations.mjs +46 -41
  372. package/scripts/db/ssl-config.mjs +194 -0
  373. package/scripts/db/theme-location.mjs +69 -0
  374. package/scripts/db/verify-tables.mjs +3 -8
  375. package/scripts/db/verify-teams.mjs +2 -6
  376. package/scripts/db/verify-theme-migrations.mjs +169 -0
  377. package/scripts/maintenance/core-updater.mjs +1003 -0
  378. package/scripts/maintenance/step-guard.mjs +74 -0
  379. package/scripts/maintenance/update-core.mjs +14 -831
  380. package/scripts/utils/console-guard.mjs +13 -0
  381. package/scripts/utils/generated-tag.mjs +66 -0
  382. package/scripts/utils/index.mjs +9 -1
  383. package/scripts/utils/logging.mjs +140 -0
  384. package/templates/app/(auth)/accept-invite/[token]/page.tsx +4 -3
  385. package/templates/app/(auth)/verify-email/page.tsx +4 -3
  386. package/templates/app/(public)/docs/[section]/[page]/page.tsx +11 -0
  387. package/templates/app/api/auth/[...all]/route.ts +15 -14
  388. package/templates/app/api/internal/user-metadata/route.ts +6 -8
  389. package/templates/app/api/user/plan-flags/route.ts +1 -1
  390. package/templates/app/api/user/profile/route.ts +12 -17
  391. package/templates/app/api/v1/api-keys/route.ts +1 -1
  392. package/templates/app/api/v1/auth/signup-with-invite/route.ts +2 -1
  393. package/templates/app/api/v1/billing/cancel/route.ts +1 -1
  394. package/templates/app/api/v1/billing/change-plan/route.ts +1 -1
  395. package/templates/app/api/v1/billing/check-action/route.ts +1 -1
  396. package/templates/app/api/v1/billing/checkout/route.ts +1 -1
  397. package/templates/app/api/v1/blocks/validate/route.ts +1 -1
  398. package/templates/app/api/v1/media/[id]/tags/route.ts +1 -1
  399. package/templates/app/api/v1/post-categories/[id]/route.ts +1 -1
  400. package/templates/app/api/v1/post-categories/route.ts +1 -1
  401. package/templates/app/api/v1/teams/[teamId]/members/route.ts +2 -1
  402. package/templates/app/api/v1/teams/[teamId]/subscription/route.ts +3 -5
  403. package/templates/app/api/v1/teams/docs.md +4 -9
  404. package/templates/app/api/v1/teams/switch/route.ts +1 -1
  405. package/templates/app/api/v1/users/[id]/meta/[key]/route.ts +1 -1
  406. package/templates/app/api/v1/users/[id]/route.ts +1 -1
  407. package/templates/app/api/v1/users/route.ts +1 -1
  408. package/templates/app/dashboard/(main)/media/page.tsx +3 -1
  409. package/templates/app/dashboard/settings/api-keys/page.tsx +5 -4
  410. package/templates/app/dashboard/settings/profile/page.tsx +3 -2
  411. package/templates/app/devtools/scheduled-actions/page.tsx +2 -1
  412. package/templates/app/superadmin/docs/[section]/[page]/page.tsx +16 -0
  413. package/templates/app/superadmin/subscriptions/page.tsx +3 -2
  414. package/templates/app/superadmin/teams/[teamId]/page.tsx +5 -4
  415. package/templates/app/superadmin/users/[userId]/page.tsx +2 -1
  416. package/templates/app/superadmin/users/page.tsx +2 -1
  417. package/templates/blocks/cta-section/component.tsx +3 -2
  418. package/templates/blocks/cta-section/schema.ts +1 -1
  419. package/templates/blocks/features-grid/component.tsx +2 -1
  420. package/templates/blocks/features-grid/schema.ts +1 -1
  421. package/templates/blocks/hero/component.tsx +3 -2
  422. package/templates/blocks/hero/schema.ts +1 -1
  423. package/templates/blocks/testimonials/component.tsx +2 -1
  424. package/templates/blocks/testimonials/schema.ts +1 -1
  425. package/templates/blocks/text-content/component.tsx +2 -1
  426. package/templates/blocks/text-content/schema.ts +1 -1
  427. package/templates/ci-workflows/README.md +1 -1
  428. package/templates/ci-workflows/github/cypress-regression.yml +1 -1
  429. package/templates/ci-workflows/github/cypress-smoke.yml +1 -1
  430. package/templates/contents/plugins/starter/plugin/api/example/route.ts.template +1 -1
  431. package/templates/contents/themes/starter/blocks/cta-section/component.tsx +3 -2
  432. package/templates/contents/themes/starter/blocks/cta-section/schema.ts +1 -1
  433. package/templates/contents/themes/starter/blocks/features-grid/component.tsx +2 -1
  434. package/templates/contents/themes/starter/blocks/features-grid/schema.ts +1 -1
  435. package/templates/contents/themes/starter/blocks/hero/component.tsx +3 -2
  436. package/templates/contents/themes/starter/blocks/hero/schema.ts +1 -1
  437. package/templates/contents/themes/starter/blocks/testimonials/component.tsx +3 -2
  438. package/templates/contents/themes/starter/blocks/testimonials/schema.ts +1 -1
  439. package/templates/contents/themes/starter/blocks/text-content/component.tsx +2 -1
  440. package/templates/contents/themes/starter/blocks/text-content/schema.ts +1 -1
  441. package/templates/contents/themes/starter/entities/posts/components/post-header.tsx +2 -1
  442. package/templates/contents/themes/starter/lib/hooks/useAnalyticsData.ts +2 -1
  443. package/templates/contents/themes/starter/lib/hooks/useTaskStats.ts +2 -1
  444. package/templates/eslint.config.mjs +133 -9
  445. package/templates/features/blog/blocks/post-content/component.tsx +2 -1
  446. package/templates/features/blog/blocks/post-content/schema.ts +1 -1
  447. package/templates/features/blog/entities/posts/components/post-header.tsx +2 -1
  448. package/templates/features/pages/blocks/hero/component.tsx +3 -2
  449. package/templates/features/pages/blocks/hero/schema.ts +1 -1
  450. package/templates/next.config.mjs +50 -29
  451. package/templates/proxy.ts +100 -21
  452. package/templates/public/sw.js +2 -2
  453. package/templates/scripts/cy-run-prod.cjs +5 -5
  454. package/tests/jest/__mocks__/@nextsparkjs/registries/template-registry.client.ts +11 -0
  455. package/tests/jest/__mocks__/next-server.js +10 -0
  456. package/scripts/build/docs.mjs +0 -392
@@ -20,9 +20,11 @@ import { join, dirname } from 'node:path'
20
20
  import {
21
21
  SEGMENT_CONFIG_EXPORTS,
22
22
  MODULE_LEVEL_EXPORTS,
23
+ analyzeTemplates,
23
24
  extractRouteExports,
24
25
  generateTemplatePage,
25
- generateMissingPages,
26
+ selectTypeScriptModule,
27
+ willGenerateRoute,
26
28
  } from '../post-build/page-generator.mjs'
27
29
 
28
30
  const FIXTURE_FILE = '/virtual/theme/templates/(public)/page.tsx'
@@ -34,13 +36,11 @@ async function createProjectRoot() {
34
36
  return mkdtemp(join(tmpdir(), 'nextspark-page-generator-test-'))
35
37
  }
36
38
 
37
- // generateTemplatePage resolves '@/...' template paths against the
38
- // generator's module-level rootDir, which is only ever set as a side effect
39
- // of generateMissingPages. Templates being empty makes it a no-op beyond
40
- // that assignment (cleanupOrphanedTemplates returns early when app/(templates)
41
- // doesn't exist yet under the given root).
42
- async function useProjectRoot(root) {
43
- await generateMissingPages([], { projectRoot: root })
39
+ // The registry build parses templates once, with analyzeTemplates, and the page
40
+ // generator renders from that analysis. Generating a single page does the same.
41
+ async function generatePage(template, outputPath, root) {
42
+ const analysis = await analyzeTemplates([template], { projectRoot: root })
43
+ await generateTemplatePage(template, outputPath, analysis)
44
44
  }
45
45
 
46
46
  async function writeThemeTemplate(root, relativePath, content) {
@@ -198,7 +198,6 @@ test('the error names the line the bad export is declared on', async () => {
198
198
  test('the generated page re-declares segment config as a literal and forwards module exports separately', async () => {
199
199
  const root = await createProjectRoot()
200
200
  try {
201
- await useProjectRoot(root)
202
201
  const templatePath = await writeThemeTemplate(
203
202
  root,
204
203
  '(public)/blog/[slug]/page.tsx',
@@ -206,9 +205,10 @@ test('the generated page re-declares segment config as a literal and forwards mo
206
205
  )
207
206
 
208
207
  const outputPath = join(root, 'app/(templates)/(public)/blog/[slug]/page.tsx')
209
- await generateTemplatePage(
208
+ await generatePage(
210
209
  { appPath: 'app/(public)/blog/[slug]/page.tsx', templateType: 'page', name: '(public)/blog/[slug]/page', templatePath },
211
- outputPath
210
+ outputPath,
211
+ root
212
212
  )
213
213
 
214
214
  const generated = await readFile(outputPath, 'utf8')
@@ -228,13 +228,13 @@ test('the generated page re-declares segment config as a literal and forwards mo
228
228
  test('a page with no route-level exports is generated unchanged (no forwarded-exports section)', async () => {
229
229
  const root = await createProjectRoot()
230
230
  try {
231
- await useProjectRoot(root)
232
231
  const templatePath = await writeThemeTemplate(root, 'plain/page.tsx', 'export default function Page() { return null }\n')
233
232
 
234
233
  const outputPath = join(root, 'app/(templates)/plain/page.tsx')
235
- await generateTemplatePage(
234
+ await generatePage(
236
235
  { appPath: 'app/plain/page.tsx', templateType: 'page', name: 'plain/page', templatePath },
237
- outputPath
236
+ outputPath,
237
+ root
238
238
  )
239
239
 
240
240
  const generated = await readFile(outputPath, 'utf8')
@@ -249,7 +249,6 @@ test('a page with no route-level exports is generated unchanged (no forwarded-ex
249
249
  test('an invalid segment config value fails page generation instead of being silently dropped', async () => {
250
250
  const root = await createProjectRoot()
251
251
  try {
252
- await useProjectRoot(root)
253
252
  const templatePath = await writeThemeTemplate(
254
253
  root,
255
254
  'broken/page.tsx',
@@ -259,9 +258,10 @@ test('an invalid segment config value fails page generation instead of being sil
259
258
  const outputPath = join(root, 'app/(templates)/broken/page.tsx')
260
259
  await assert.rejects(
261
260
  () =>
262
- generateTemplatePage(
261
+ generatePage(
263
262
  { appPath: 'app/broken/page.tsx', templateType: 'page', name: 'broken/page', templatePath },
264
- outputPath
263
+ outputPath,
264
+ root
265
265
  ),
266
266
  /"revalidate"/
267
267
  )
@@ -273,7 +273,6 @@ test('an invalid segment config value fails page generation instead of being sil
273
273
  test('a layout with a default export and metadata forwards both, unlike the previous component-only behavior', async () => {
274
274
  const root = await createProjectRoot()
275
275
  try {
276
- await useProjectRoot(root)
277
276
  const templatePath = await writeThemeTemplate(
278
277
  root,
279
278
  'dashboard/layout.tsx',
@@ -281,9 +280,10 @@ test('a layout with a default export and metadata forwards both, unlike the prev
281
280
  )
282
281
 
283
282
  const outputPath = join(root, 'app/(templates)/dashboard/layout.tsx')
284
- await generateTemplatePage(
283
+ await generatePage(
285
284
  { appPath: 'app/dashboard/layout.tsx', templateType: 'layout', name: 'dashboard/layout', templatePath },
286
- outputPath
285
+ outputPath,
286
+ root
287
287
  )
288
288
 
289
289
  const generated = await readFile(outputPath, 'utf8')
@@ -297,13 +297,13 @@ test('a layout with a default export and metadata forwards both, unlike the prev
297
297
  test('a layout template with no exports at all still gets the plain pass-through wrapper', async () => {
298
298
  const root = await createProjectRoot()
299
299
  try {
300
- await useProjectRoot(root)
301
300
  const templatePath = await writeThemeTemplate(root, 'empty/layout.tsx', '')
302
301
 
303
302
  const outputPath = join(root, 'app/(templates)/empty/layout.tsx')
304
- await generateTemplatePage(
303
+ await generatePage(
305
304
  { appPath: 'app/empty/layout.tsx', templateType: 'layout', name: 'empty/layout', templatePath },
306
- outputPath
305
+ outputPath,
306
+ root
307
307
  )
308
308
 
309
309
  const generated = await readFile(outputPath, 'utf8')
@@ -335,7 +335,6 @@ for (const [label, source, expected] of [
335
335
  test('a metadata-only layout whose comment mentions export default gets the pass-through, not an import of a missing default', async () => {
336
336
  const root = await createProjectRoot()
337
337
  try {
338
- await useProjectRoot(root)
339
338
  const templatePath = await writeThemeTemplate(
340
339
  root,
341
340
  'docs/layout.tsx',
@@ -343,13 +342,14 @@ test('a metadata-only layout whose comment mentions export default gets the pass
343
342
  )
344
343
 
345
344
  const outputPath = join(root, 'app/(templates)/docs/layout.tsx')
346
- await generateTemplatePage(
345
+ await generatePage(
347
346
  { appPath: 'app/docs/layout.tsx', templateType: 'layout', name: 'docs/layout', templatePath },
348
- outputPath
347
+ outputPath,
348
+ root
349
349
  )
350
350
 
351
351
  const generated = await readFile(outputPath, 'utf8')
352
- assert.match(generated, /Layout template - metadata-only/)
352
+ assert.match(generated, /Pass-through component/)
353
353
  assert.doesNotMatch(generated, /import TemplateComponent from/)
354
354
  assert.match(generated, /export \{ metadata \} from/)
355
355
  } finally {
@@ -357,10 +357,45 @@ test('a metadata-only layout whose comment mentions export default gets the pass
357
357
  }
358
358
  })
359
359
 
360
+ test('a pass-through layout on an unprotected path does not claim to be PROTECTED_RENDER', async () => {
361
+ const root = await createProjectRoot()
362
+ try {
363
+ const templatePath = await writeThemeTemplate(root, '(public)/docs/layout.tsx', "export const viewport = { themeColor: 'black' }\n")
364
+
365
+ const outputPath = join(root, 'app/(templates)/(public)/docs/layout.tsx')
366
+ await generatePage(
367
+ { appPath: 'app/(public)/docs/layout.tsx', templateType: 'layout', name: '(public)/docs/layout', templatePath },
368
+ outputPath,
369
+ root
370
+ )
371
+
372
+ const generated = await readFile(outputPath, 'utf8')
373
+ assert.match(generated, /Pass-through component \(the theme template exports no component/)
374
+ assert.doesNotMatch(generated, /PROTECTED_RENDER/)
375
+ } finally {
376
+ await rm(root, { recursive: true, force: true })
377
+ }
378
+ })
379
+
380
+ test('a pass-through layout on a PROTECTED_RENDER path is labeled as one', async () => {
381
+ const root = await createProjectRoot()
382
+ try {
383
+ const templatePath = await writeThemeTemplate(root, 'layout.tsx', "export const metadata = { title: 'App' }\n")
384
+
385
+ const outputPath = join(root, 'app/(templates)/layout.tsx')
386
+ await generatePage({ appPath: 'app/layout.tsx', templateType: 'layout', name: 'layout', templatePath }, outputPath, root)
387
+
388
+ const generated = await readFile(outputPath, 'utf8')
389
+ assert.match(generated, /Layout template - metadata-only \(PROTECTED_RENDER\)/)
390
+ assert.match(generated, /Pass-through component \(actual rendering blocked by PROTECTED_RENDER\)/)
391
+ } finally {
392
+ await rm(root, { recursive: true, force: true })
393
+ }
394
+ })
395
+
360
396
  test('a layout exported through `export { X as default }` imports the theme component instead of a pass-through', async () => {
361
397
  const root = await createProjectRoot()
362
398
  try {
363
- await useProjectRoot(root)
364
399
  const templatePath = await writeThemeTemplate(
365
400
  root,
366
401
  'dashboard/layout.tsx',
@@ -368,9 +403,10 @@ test('a layout exported through `export { X as default }` imports the theme comp
368
403
  )
369
404
 
370
405
  const outputPath = join(root, 'app/(templates)/dashboard/layout.tsx')
371
- await generateTemplatePage(
406
+ await generatePage(
372
407
  { appPath: 'app/dashboard/layout.tsx', templateType: 'layout', name: 'dashboard/layout', templatePath },
373
- outputPath
408
+ outputPath,
409
+ root
374
410
  )
375
411
 
376
412
  const generated = await readFile(outputPath, 'utf8')
@@ -392,7 +428,6 @@ test('a .ts template is parsed as TypeScript, so syntax a TSX parse reads as JSX
392
428
  test('a .ts layout with TypeScript-only syntax imports the theme component instead of a pass-through', async () => {
393
429
  const root = await createProjectRoot()
394
430
  try {
395
- await useProjectRoot(root)
396
431
  const templatePath = await writeThemeTemplate(
397
432
  root,
398
433
  'reports/layout.ts',
@@ -400,9 +435,10 @@ test('a .ts layout with TypeScript-only syntax imports the theme component inste
400
435
  )
401
436
 
402
437
  const outputPath = join(root, 'app/(templates)/reports/layout.ts')
403
- await generateTemplatePage(
438
+ await generatePage(
404
439
  { appPath: 'app/reports/layout.ts', templateType: 'layout', name: 'reports/layout', templatePath },
405
- outputPath
440
+ outputPath,
441
+ root
406
442
  )
407
443
 
408
444
  const generated = await readFile(outputPath, 'utf8')
@@ -420,11 +456,149 @@ test('a template that does not parse fails with its file and line instead of bei
420
456
  )
421
457
  })
422
458
 
423
- test('syntax Next.js parses but the installed TypeScript does not (a source phase import) is read instead of rejected', async () => {
459
+ test('a template using syntax TypeScript rejects but Next.js accepts retains its route exports', async () => {
424
460
  const source = "import source wasm from './module.wasm'\nexport const runtime = 'edge'\nexport default wasm\n"
425
461
  for (const file of ['/virtual/theme/templates/(public)/page.ts', FIXTURE_FILE]) {
426
- const { segmentConfig, hasDefaultExport } = await extractRouteExports(source, file)
427
- assert.deepEqual(segmentConfig, { runtime: 'edge' }, file)
428
- assert.equal(hasDefaultExport, true, file)
462
+ const routeExports = await extractRouteExports(source, file)
463
+ assert.deepEqual(routeExports.segmentConfig, { runtime: 'edge' })
464
+ assert.equal(routeExports.hasDefaultExport, true)
465
+ }
466
+ })
467
+
468
+ test('a syntax error rejected by both TypeScript and Next.js is rejected even when TypeScript recovers later route exports', async () => {
469
+ const source = "import = './module.wasm'\nexport const runtime = 'edge'\nexport default wasm\n"
470
+ for (const file of ['/virtual/theme/templates/(public)/page.ts', FIXTURE_FILE]) {
471
+ await assert.rejects(() => extractRouteExports(source, file), /the template does not parse/)
472
+ }
473
+ })
474
+
475
+ // --- selectTypeScriptModule: picking a candidate with the TS 5/6 compiler API (#195) --
476
+
477
+ // A stand-in for TypeScript 7's native port: it has a version but none of the
478
+ // compiler API (`createSourceFile`, `getScriptKindFromFileName`, `ScriptKind`)
479
+ // this generator parses templates with.
480
+ const fakeTypeScript7 = { version: '7.0.2' }
481
+
482
+ const fakeTypeScript5 = {
483
+ version: '5.9.3',
484
+ createSourceFile: () => ({}),
485
+ getScriptKindFromFileName: () => 1,
486
+ ScriptKind: { TSX: 4 },
487
+ }
488
+
489
+ test('a candidate with the compiler API is used', async () => {
490
+ const ts = await selectTypeScriptModule([{ label: 'only candidate', load: async () => fakeTypeScript5 }])
491
+ assert.equal(ts, fakeTypeScript5)
492
+ })
493
+
494
+ test('a candidate module shaped like an ESM default export is unwrapped', async () => {
495
+ const ts = await selectTypeScriptModule([{ label: 'only candidate', load: async () => ({ default: fakeTypeScript5 }) }])
496
+ assert.equal(ts, fakeTypeScript5)
497
+ })
498
+
499
+ test('a TypeScript 7 candidate without the compiler API is skipped in favor of the next candidate that has it', async () => {
500
+ const ts = await selectTypeScriptModule([
501
+ { label: '@nextsparkjs/core', load: async () => fakeTypeScript7 },
502
+ { label: 'the project', load: async () => fakeTypeScript5 },
503
+ ])
504
+ assert.equal(ts, fakeTypeScript5)
505
+ })
506
+
507
+ test('a candidate whose load() rejects is skipped in favor of the next candidate', async () => {
508
+ const ts = await selectTypeScriptModule([
509
+ { label: '@nextsparkjs/core', load: async () => { throw new Error('Cannot find module \'typescript\'') } },
510
+ { label: 'the project', load: async () => fakeTypeScript5 },
511
+ ])
512
+ assert.equal(ts, fakeTypeScript5)
513
+ })
514
+
515
+ test('no candidate providing the compiler API fails with a message naming each candidate and what was found', async () => {
516
+ await assert.rejects(
517
+ () =>
518
+ selectTypeScriptModule([
519
+ { label: '@nextsparkjs/core', load: async () => fakeTypeScript7 },
520
+ { label: 'the project at /virtual/project', load: async () => { throw new Error('Cannot find module \'typescript\'') } },
521
+ ]),
522
+ error => {
523
+ assert.match(error.message, /TypeScript 5 or 6 compiler API/)
524
+ assert.match(error.message, /@nextsparkjs\/core.*TypeScript 7\.0\.2/)
525
+ assert.match(error.message, /the project at \/virtual\/project.*could not be loaded/)
526
+ assert.doesNotMatch(error.message, /createSourceFile is not a function/)
527
+ return true
528
+ }
529
+ )
530
+ })
531
+
532
+ // --- willGenerateRoute: whether the page generator writes a route file importing the template (#197) --
533
+
534
+ test('willGenerateRoute is always true for a layout, whether or not the app already has one', async () => {
535
+ const root = await createProjectRoot()
536
+ try {
537
+ assert.equal(willGenerateRoute('app/dashboard/layout.tsx', 'layout', root), true)
538
+
539
+ await mkdir(join(root, 'app/dashboard'), { recursive: true })
540
+ await writeFile(join(root, 'app/dashboard/layout.tsx'), 'export default function Layout({ children }) { return children }\n', 'utf8')
541
+ assert.equal(willGenerateRoute('app/dashboard/layout.tsx', 'layout', root), true)
542
+ } finally {
543
+ await rm(root, { recursive: true, force: true })
544
+ }
545
+ })
546
+
547
+ // --- analyzeTemplates: what the build reads from each template, and generates from (#197) --
548
+
549
+ test('analyzeTemplates records, per template path, its route-level exports, whether it has a default export and whether a route file is generated', async () => {
550
+ const root = await createProjectRoot()
551
+ try {
552
+ const layoutPath = await writeThemeTemplate(root, 'docs/layout.tsx', "export const revalidate = 60\nexport const viewport = { themeColor: 'black' }\n")
553
+ const pagePath = await writeThemeTemplate(root, 'pricing/page.tsx', 'export default function Page() { return null }\n')
554
+ await mkdir(join(root, 'app/pricing'), { recursive: true })
555
+ await writeFile(join(root, 'app/pricing/page.tsx'), 'export default function Page() { return null }\n', 'utf8')
556
+ const layout = { appPath: 'app/docs/layout.tsx', templateType: 'layout', name: 'docs/layout', templatePath: layoutPath }
557
+ const page = { appPath: 'app/pricing/page.tsx', templateType: 'page', name: 'pricing/page', templatePath: pagePath }
558
+
559
+ const analysis = await analyzeTemplates([layout, page, { ...page }], { projectRoot: root })
560
+
561
+ assert.deepEqual([...analysis.keys()], [layoutPath, pagePath])
562
+ assert.deepEqual(analysis.get(layoutPath), {
563
+ segmentConfig: { revalidate: 60 },
564
+ moduleExports: ['viewport'],
565
+ hasDefaultExport: false,
566
+ generatesRoute: true,
567
+ })
568
+ assert.deepEqual(analysis.get(pagePath), { segmentConfig: {}, moduleExports: [], hasDefaultExport: true, generatesRoute: false })
569
+ } finally {
570
+ await rm(root, { recursive: true, force: true })
571
+ }
572
+ })
573
+
574
+ test('generating a page from an analysis that never read its template fails instead of reading the template', async () => {
575
+ const root = await createProjectRoot()
576
+ try {
577
+ const templatePath = await writeThemeTemplate(root, 'plain/page.tsx', 'export default function Page() { return null }\n')
578
+
579
+ await assert.rejects(
580
+ () =>
581
+ generateTemplatePage(
582
+ { appPath: 'app/plain/page.tsx', templateType: 'page', name: 'plain/page', templatePath },
583
+ join(root, 'app/(templates)/plain/page.tsx'),
584
+ new Map()
585
+ ),
586
+ /was not read by analyzeTemplates/
587
+ )
588
+ } finally {
589
+ await rm(root, { recursive: true, force: true })
590
+ }
591
+ })
592
+
593
+ test('willGenerateRoute is true for a page only when the app has no file there yet', async () => {
594
+ const root = await createProjectRoot()
595
+ try {
596
+ assert.equal(willGenerateRoute('app/docs/page.tsx', 'page', root), true)
597
+
598
+ await mkdir(join(root, 'app/docs'), { recursive: true })
599
+ await writeFile(join(root, 'app/docs/page.tsx'), 'export default function Page() { return null }\n', 'utf8')
600
+ assert.equal(willGenerateRoute('app/docs/page.tsx', 'page', root), false)
601
+ } finally {
602
+ await rm(root, { recursive: true, force: true })
429
603
  }
430
604
  })
@@ -0,0 +1,239 @@
1
+ /**
2
+ * The template generators @nextsparkjs/core exposes through `./scripts/*`
3
+ * keep the signatures they were published with. A caller that doesn't pass
4
+ * the analysis `analyzeTemplates` produces gets one taken for that call, with
5
+ * the same output the registry build produces, and no generator deletes
6
+ * anything before its input has been checked.
7
+ *
8
+ * Run: node --test packages/core/scripts/build/registry/__tests__/published-signatures.test.mjs
9
+ */
10
+
11
+ import { test } from 'node:test'
12
+ import assert from 'node:assert/strict'
13
+ import { mkdtemp, mkdir, writeFile, readFile, rm } from 'node:fs/promises'
14
+ import { existsSync } from 'node:fs'
15
+ import { tmpdir } from 'node:os'
16
+ import { join, dirname } from 'node:path'
17
+
18
+ import {
19
+ generateTemplateRegistry,
20
+ generateTemplateRegistryClient
21
+ } from '../generators/template-registry.mjs'
22
+ import { generateMissingPages, generateTemplatePage } from '../post-build/page-generator.mjs'
23
+
24
+ async function createProject() {
25
+ return mkdtemp(join(tmpdir(), 'nextspark-published-signatures-test-'))
26
+ }
27
+
28
+ async function writeProjectFile(root, relativePath, content) {
29
+ const absolutePath = join(root, relativePath)
30
+ await mkdir(dirname(absolutePath), { recursive: true })
31
+ await writeFile(absolutePath, content, 'utf8')
32
+ }
33
+
34
+ /** A theme template at `contents/themes/testtheme/templates/<relativePath>`, overriding `app/<relativePath>`. */
35
+ async function writeTemplate(root, relativePath, templateType, content) {
36
+ await writeProjectFile(root, join('contents/themes/testtheme/templates', relativePath), content)
37
+ return {
38
+ name: relativePath.replace(/\.(tsx|ts)$/, ''),
39
+ themeName: 'testtheme',
40
+ templateType,
41
+ fileName: relativePath.split('/').pop(),
42
+ relativePath,
43
+ appPath: `app/${relativePath}`,
44
+ templatePath: `@/contents/themes/testtheme/templates/${relativePath}`,
45
+ priority: 10
46
+ }
47
+ }
48
+
49
+ const LAYOUT_WITHOUT_COMPONENT = "export const viewport = { themeColor: 'black' }\n"
50
+ const PAGE_WITH_COMPONENT = 'export default function Page() { return null }\n'
51
+
52
+ test('generateTemplateRegistry(templates, config) decides component vs metadata-only without being handed an analysis', async () => {
53
+ const root = await createProject()
54
+ try {
55
+ const layout = await writeTemplate(root, 'docs/layout.tsx', 'layout', LAYOUT_WITHOUT_COMPONENT)
56
+ const page = await writeTemplate(root, 'pricing/page.tsx', 'page', PAGE_WITH_COMPONENT)
57
+
58
+ const out = await generateTemplateRegistry([layout, page], { outputDir: join(root, '.nextspark/registries'), projectRoot: root })
59
+
60
+ assert.match(out, /'app\/docs\/layout\.tsx': \{\n\s*appPath: 'app\/docs\/layout\.tsx',\n\s*component: null,/)
61
+ assert.match(out, /component: lazyTemplate\('app\/pricing\/page\.tsx', \(\) => import\('@\/contents\/themes\/testtheme\/templates\/pricing\/page'\)\)/)
62
+ } finally {
63
+ await rm(root, { recursive: true, force: true })
64
+ }
65
+ })
66
+
67
+ test('generateTemplateRegistryClient(templates, config) leaves out a template with no component without being handed an analysis', async () => {
68
+ const root = await createProject()
69
+ try {
70
+ const layout = await writeTemplate(root, 'docs/layout.tsx', 'layout', LAYOUT_WITHOUT_COMPONENT)
71
+ const page = await writeTemplate(root, 'pricing/page.tsx', 'page', PAGE_WITH_COMPONENT)
72
+
73
+ const out = await generateTemplateRegistryClient([layout, page], { outputDir: join(root, '.nextspark/registries'), projectRoot: root })
74
+
75
+ assert.doesNotMatch(out, /app\/docs\/layout\.tsx/)
76
+ assert.match(out, /'app\/pricing\/page\.tsx': dynamic\(\(\) => import\('@\/contents\/themes\/testtheme\/templates\/pricing\/page'\)\)/)
77
+ } finally {
78
+ await rm(root, { recursive: true, force: true })
79
+ }
80
+ })
81
+
82
+ test('generateMissingPages(templates, config) writes the same routes the registry build does without being handed an analysis', async () => {
83
+ const root = await createProject()
84
+ try {
85
+ const layout = await writeTemplate(root, 'docs/layout.tsx', 'layout', LAYOUT_WITHOUT_COMPONENT)
86
+ const page = await writeTemplate(root, 'pricing/page.tsx', 'page', PAGE_WITH_COMPONENT)
87
+
88
+ await generateMissingPages([layout, page], { projectRoot: root })
89
+
90
+ const generatedLayout = await readFile(join(root, 'app/(templates)/docs/layout.tsx'), 'utf8')
91
+ assert.match(generatedLayout, /Pass-through component/)
92
+ assert.match(generatedLayout, /export \{ viewport \} from '@\/contents\/themes\/testtheme\/templates\/docs\/layout'/)
93
+
94
+ const generatedPage = await readFile(join(root, 'app/(templates)/pricing/page.tsx'), 'utf8')
95
+ assert.match(generatedPage, /import TemplateComponent from '@\/contents\/themes\/testtheme\/templates\/pricing\/page'/)
96
+ } finally {
97
+ await rm(root, { recursive: true, force: true })
98
+ }
99
+ })
100
+
101
+ test('generateTemplatePage(template, outputPath) writes a route file without being handed an analysis', async () => {
102
+ const root = await createProject()
103
+ try {
104
+ // generateTemplatePage resolves templates against the project root the last
105
+ // generateMissingPages call was given.
106
+ await generateMissingPages([], { projectRoot: root })
107
+ const page = await writeTemplate(root, 'blog/page.tsx', 'page', `export const revalidate = 3600\n${PAGE_WITH_COMPONENT}`)
108
+ const outputPath = join(root, 'app/(templates)/blog/page.tsx')
109
+
110
+ await generateTemplatePage(page, outputPath)
111
+
112
+ const generated = await readFile(outputPath, 'utf8')
113
+ assert.match(generated, /import TemplateComponent from '@\/contents\/themes\/testtheme\/templates\/blog\/page'/)
114
+ assert.match(generated, /export const revalidate = 3600/)
115
+ } finally {
116
+ await rm(root, { recursive: true, force: true })
117
+ }
118
+ })
119
+
120
+ test('generateTemplatePage(template, outputPath) still rejects segment config Next.js would not read', async () => {
121
+ const root = await createProject()
122
+ try {
123
+ await generateMissingPages([], { projectRoot: root })
124
+ const page = await writeTemplate(root, 'broken/page.tsx', 'page', `export const revalidate = 60 * 60\n${PAGE_WITH_COMPONENT}`)
125
+ const outputPath = join(root, 'app/(templates)/broken/page.tsx')
126
+
127
+ await assert.rejects(() => generateTemplatePage(page, outputPath), /segment config export "revalidate" is not a literal/)
128
+ assert.equal(existsSync(outputPath), false)
129
+ } finally {
130
+ await rm(root, { recursive: true, force: true })
131
+ }
132
+ })
133
+
134
+ test('generateMissingPages(templates, config) rejects a template it cannot generate before deleting anything', async () => {
135
+ const root = await createProject()
136
+ try {
137
+ await writeProjectFile(root, 'app/(templates)/old/page.tsx', PAGE_WITH_COMPONENT)
138
+ const page = await writeTemplate(root, 'docs/page.tsx', 'page', "export const metadata = { title: 'Docs' }\n")
139
+
140
+ await assert.rejects(() => generateMissingPages([page], { projectRoot: root }), /has no default export/)
141
+
142
+ assert.equal(existsSync(join(root, 'app/(templates)/old/page.tsx')), true)
143
+ } finally {
144
+ await rm(root, { recursive: true, force: true })
145
+ }
146
+ })
147
+
148
+ test('generateMissingPages rejects an analysis that is missing one of its templates before deleting anything', async () => {
149
+ const root = await createProject()
150
+ try {
151
+ await writeProjectFile(root, 'app/(templates)/old/page.tsx', PAGE_WITH_COMPONENT)
152
+ const page = await writeTemplate(root, 'pricing/page.tsx', 'page', PAGE_WITH_COMPONENT)
153
+
154
+ await assert.rejects(() => generateMissingPages([page], { projectRoot: root }, new Map()), /was not read by analyzeTemplates/)
155
+
156
+ assert.equal(existsSync(join(root, 'app/(templates)/old/page.tsx')), true)
157
+ } finally {
158
+ await rm(root, { recursive: true, force: true })
159
+ }
160
+ })
161
+
162
+ /** A standalone `page.meta.ts` - which never has a default export - overriding `app/<route>/page.tsx`. */
163
+ async function writeMetaTemplate(root, route) {
164
+ const relativePath = `${route}/page.meta.ts`
165
+ await writeProjectFile(root, join('contents/themes/testtheme/templates', relativePath), "export const metadata = { title: 'Docs' }\n")
166
+ return {
167
+ name: `${route}/page`,
168
+ themeName: 'testtheme',
169
+ templateType: 'page',
170
+ fileName: 'page.meta.ts',
171
+ relativePath,
172
+ appPath: `app/${route}/page.tsx`,
173
+ templatePath: `@/contents/themes/testtheme/templates/${relativePath}`,
174
+ priority: 10
175
+ }
176
+ }
177
+
178
+ test('generateTemplatePage(template, outputPath) rejects a page.meta.ts for a route the app has no page at, without writing it', async () => {
179
+ const root = await createProject()
180
+ try {
181
+ await generateMissingPages([], { projectRoot: root })
182
+ const meta = await writeMetaTemplate(root, 'docs')
183
+ const outputPath = join(root, 'app/(templates)/docs/page.tsx')
184
+
185
+ await assert.rejects(
186
+ () => generateTemplatePage(meta, outputPath),
187
+ /page\.meta\.ts has no default export, and the app has no existing route at "app\/docs\/page\.tsx"/
188
+ )
189
+ assert.equal(existsSync(outputPath), false)
190
+ } finally {
191
+ await rm(root, { recursive: true, force: true })
192
+ }
193
+ })
194
+
195
+ test('generateTemplatePage(template, outputPath) rejects a page.tsx with no default export for a route the app has no page at, without writing it', async () => {
196
+ const root = await createProject()
197
+ try {
198
+ await generateMissingPages([], { projectRoot: root })
199
+ const page = await writeTemplate(root, 'docs/page.tsx', 'page', "export const metadata = { title: 'Docs' }\n")
200
+ const outputPath = join(root, 'app/(templates)/docs/page.tsx')
201
+
202
+ await assert.rejects(
203
+ () => generateTemplatePage(page, outputPath),
204
+ /docs\/page\.tsx has no default export, and the app has no existing route at "app\/docs\/page\.tsx"/
205
+ )
206
+ assert.equal(existsSync(outputPath), false)
207
+ } finally {
208
+ await rm(root, { recursive: true, force: true })
209
+ }
210
+ })
211
+
212
+ test('generateMissingPages(templates, config) rejects a page.meta.ts for a route the app has no page at, without writing anything', async () => {
213
+ const root = await createProject()
214
+ try {
215
+ const meta = await writeMetaTemplate(root, 'docs')
216
+
217
+ await assert.rejects(() => generateMissingPages([meta], { projectRoot: root }), /page\.meta\.ts has no default export/)
218
+
219
+ assert.equal(existsSync(join(root, 'app/(templates)')), false)
220
+ } finally {
221
+ await rm(root, { recursive: true, force: true })
222
+ }
223
+ })
224
+
225
+ test('a page.meta.ts over a page the app already has is accepted, and no route file is written for it', async () => {
226
+ const root = await createProject()
227
+ try {
228
+ await writeProjectFile(root, 'app/docs/page.tsx', PAGE_WITH_COMPONENT)
229
+ const meta = await writeMetaTemplate(root, 'docs')
230
+
231
+ await generateMissingPages([meta], { projectRoot: root })
232
+ assert.equal(existsSync(join(root, 'app/(templates)/docs/page.tsx')), false)
233
+
234
+ const out = await generateTemplateRegistry([meta], { outputDir: join(root, '.nextspark/registries'), projectRoot: root })
235
+ assert.match(out, /'app\/docs\/page\.tsx': \{\n\s*appPath: 'app\/docs\/page\.tsx',\n\s*component: null,/)
236
+ } finally {
237
+ await rm(root, { recursive: true, force: true })
238
+ }
239
+ })