@nextsparkjs/core 0.1.0-beta.194 → 0.1.0-beta.195

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 (338) hide show
  1. package/dist/components/app/guards/DeveloperGuard.d.ts +8 -1
  2. package/dist/components/app/guards/DeveloperGuard.d.ts.map +1 -1
  3. package/dist/components/app/guards/DeveloperGuard.js +2 -1
  4. package/dist/components/app/guards/SuperAdminGuard.d.ts +8 -1
  5. package/dist/components/app/guards/SuperAdminGuard.d.ts.map +1 -1
  6. package/dist/components/app/guards/SuperAdminGuard.js +2 -1
  7. package/dist/components/dashboard/block-editor/block-picker.d.ts.map +1 -1
  8. package/dist/components/dashboard/block-editor/block-picker.js +2 -1
  9. package/dist/components/dashboard/block-editor/builder-editor-view.d.ts.map +1 -1
  10. package/dist/components/dashboard/block-editor/builder-editor-view.js +39 -23
  11. package/dist/components/dashboard/block-editor/pattern-reference-preview.d.ts.map +1 -1
  12. package/dist/components/dashboard/block-editor/pattern-reference-preview.js +2 -1
  13. package/dist/components/dashboard/layouts/AuthGuard.d.ts.map +1 -1
  14. package/dist/components/dashboard/layouts/AuthGuard.js +3 -4
  15. package/dist/components/dashboard/layouts/AuthenticatedDashboardLayout.d.ts.map +1 -1
  16. package/dist/components/dashboard/layouts/AuthenticatedDashboardLayout.js +9 -10
  17. package/dist/components/dashboard/layouts/DashboardAuthLayout.d.ts.map +1 -1
  18. package/dist/components/dashboard/layouts/DashboardAuthLayout.js +3 -4
  19. package/dist/components/dashboard/layouts/DashboardAuthSkeleton.d.ts +2 -2
  20. package/dist/components/entities/wrappers/EntityDetailWrapper.d.ts.map +1 -1
  21. package/dist/components/entities/wrappers/EntityDetailWrapper.js +5 -2
  22. package/dist/components/entities/wrappers/EntityFormWrapper.d.ts.map +1 -1
  23. package/dist/components/entities/wrappers/EntityFormWrapper.js +5 -2
  24. package/dist/components/entities/wrappers/EntityListWrapper.d.ts.map +1 -1
  25. package/dist/components/entities/wrappers/EntityListWrapper.js +5 -2
  26. package/dist/components/ui/relation-display.d.ts.map +1 -1
  27. package/dist/components/ui/relation-display.js +2 -1
  28. package/dist/components/ui/user-display.d.ts.map +1 -1
  29. package/dist/components/ui/user-display.js +2 -1
  30. package/dist/emails/otp-verification.d.ts.map +1 -1
  31. package/dist/emails/otp-verification.js +5 -3
  32. package/dist/emails/reset-password.d.ts.map +1 -1
  33. package/dist/emails/reset-password.js +5 -3
  34. package/dist/emails/team-invitation.d.ts.map +1 -1
  35. package/dist/emails/team-invitation.js +3 -2
  36. package/dist/emails/verify-email.d.ts.map +1 -1
  37. package/dist/emails/verify-email.js +5 -3
  38. package/dist/hooks/useAuth.d.ts.map +1 -1
  39. package/dist/hooks/useAuth.js +21 -4
  40. package/dist/hooks/usePatternUsages.d.ts.map +1 -1
  41. package/dist/hooks/usePatternUsages.js +3 -2
  42. package/dist/lib/api/auth.d.ts +2 -0
  43. package/dist/lib/api/auth.d.ts.map +1 -1
  44. package/dist/lib/api/auth.js +10 -10
  45. package/dist/lib/api/client-headers.d.ts +11 -0
  46. package/dist/lib/api/client-headers.d.ts.map +1 -0
  47. package/dist/lib/api/client-headers.js +12 -0
  48. package/dist/lib/api/client-ip.d.ts +59 -0
  49. package/dist/lib/api/client-ip.d.ts.map +1 -0
  50. package/dist/lib/api/client-ip.js +91 -0
  51. package/dist/lib/api/cors-response.d.ts +54 -0
  52. package/dist/lib/api/cors-response.d.ts.map +1 -0
  53. package/dist/lib/api/cors-response.js +88 -0
  54. package/dist/lib/api/entities.d.ts.map +1 -1
  55. package/dist/lib/api/entities.js +4 -3
  56. package/dist/lib/api/entity/audit-log.d.ts.map +1 -1
  57. package/dist/lib/api/entity/audit-log.js +2 -1
  58. package/dist/lib/api/entity/generic-handler.d.ts.map +1 -1
  59. package/dist/lib/api/entity/generic-handler.js +23 -5
  60. package/dist/lib/api/helpers.d.ts +8 -2
  61. package/dist/lib/api/helpers.d.ts.map +1 -1
  62. package/dist/lib/api/helpers.js +27 -81
  63. package/dist/lib/api/keys.d.ts +6 -0
  64. package/dist/lib/api/keys.d.ts.map +1 -1
  65. package/dist/lib/api/keys.js +7 -1
  66. package/dist/lib/api/rate-limit.d.ts +4 -0
  67. package/dist/lib/api/rate-limit.d.ts.map +1 -1
  68. package/dist/lib/api/rate-limit.js +16 -40
  69. package/dist/lib/api/request-origin.d.ts +16 -7
  70. package/dist/lib/api/request-origin.d.ts.map +1 -1
  71. package/dist/lib/api/request-origin.js +43 -20
  72. package/dist/lib/auth/load-login-page.d.ts +10 -0
  73. package/dist/lib/auth/load-login-page.d.ts.map +1 -0
  74. package/dist/lib/auth/load-login-page.js +7 -0
  75. package/dist/lib/auth/runtime-readiness.d.ts.map +1 -1
  76. package/dist/lib/auth/runtime-readiness.js +2 -0
  77. package/dist/lib/auth/security-notifications/device-fingerprint.d.ts +1 -8
  78. package/dist/lib/auth/security-notifications/device-fingerprint.d.ts.map +1 -1
  79. package/dist/lib/auth/security-notifications/device-fingerprint.js +2 -12
  80. package/dist/lib/auth-email-callbacks.js +3 -3
  81. package/dist/lib/auth.d.ts.map +1 -1
  82. package/dist/lib/auth.js +7 -2
  83. package/dist/lib/config/config-client.d.ts.map +1 -1
  84. package/dist/lib/config/config-client.js +1 -1
  85. package/dist/lib/config/config-sync.d.ts.map +1 -1
  86. package/dist/lib/config/config-sync.js +1 -1
  87. package/dist/lib/config/public-config-client.d.ts +1 -0
  88. package/dist/lib/config/public-config-client.d.ts.map +1 -1
  89. package/dist/lib/config/public-config-client.js +3 -1
  90. package/dist/lib/config/types.d.ts +2 -0
  91. package/dist/lib/config/types.d.ts.map +1 -1
  92. package/dist/lib/constants/reserved-slugs.d.ts +17 -8
  93. package/dist/lib/constants/reserved-slugs.d.ts.map +1 -1
  94. package/dist/lib/constants/reserved-slugs.js +16 -52
  95. package/dist/lib/db.d.ts.map +1 -1
  96. package/dist/lib/db.js +9 -3
  97. package/dist/lib/entities/external-api-generator.d.ts.map +1 -1
  98. package/dist/lib/entities/external-api-generator.js +2 -4
  99. package/dist/lib/entities/public-slug.d.ts +46 -0
  100. package/dist/lib/entities/public-slug.d.ts.map +1 -0
  101. package/dist/lib/entities/public-slug.js +39 -0
  102. package/dist/routes/(auth)/signup/page.cc.d.ts +3 -8
  103. package/dist/routes/(auth)/signup/page.cc.d.ts.map +1 -1
  104. package/dist/routes/(auth)/signup/page.cc.js +7 -1
  105. package/dist/routes/(auth)/verify-email/page.d.ts.map +1 -1
  106. package/dist/routes/(auth)/verify-email/page.js +2 -1
  107. package/dist/routes/(public)/page.cc.d.ts +3 -0
  108. package/dist/routes/(public)/page.cc.d.ts.map +1 -0
  109. package/dist/routes/(public)/page.cc.js +3 -0
  110. package/dist/routes/_internal/area-access.cc.d.ts +21 -0
  111. package/dist/routes/_internal/area-access.cc.d.ts.map +1 -0
  112. package/dist/routes/_internal/area-access.cc.js +19 -0
  113. package/dist/routes/_internal/auth-layout.d.ts.map +1 -1
  114. package/dist/routes/_internal/auth-layout.js +3 -2
  115. package/dist/routes/_internal/dashboard-main-shared.d.ts +7 -0
  116. package/dist/routes/_internal/dashboard-main-shared.d.ts.map +1 -0
  117. package/dist/routes/_internal/dashboard-main-shared.js +49 -0
  118. package/dist/routes/_internal/default-auth-layout.d.ts.map +1 -1
  119. package/dist/routes/_internal/default-auth-layout.js +3 -2
  120. package/dist/routes/_internal/devtools-layout.cc.d.ts +3 -2
  121. package/dist/routes/_internal/devtools-layout.cc.d.ts.map +1 -1
  122. package/dist/routes/_internal/devtools-layout.cc.js +1 -1
  123. package/dist/routes/_internal/devtools-layout.d.ts +7 -2
  124. package/dist/routes/_internal/devtools-layout.d.ts.map +1 -1
  125. package/dist/routes/_internal/devtools-layout.js +4 -4
  126. package/dist/routes/_internal/entity-detail-route.cc.d.ts +8 -0
  127. package/dist/routes/_internal/entity-detail-route.cc.d.ts.map +1 -0
  128. package/dist/routes/_internal/entity-detail-route.cc.js +5 -0
  129. package/dist/routes/_internal/entity-edit-route.cc.d.ts +8 -0
  130. package/dist/routes/_internal/entity-edit-route.cc.d.ts.map +1 -0
  131. package/dist/routes/_internal/entity-edit-route.cc.js +5 -0
  132. package/dist/routes/_internal/public-archive-route.d.ts +1 -1
  133. package/dist/routes/_internal/public-archive-route.d.ts.map +1 -1
  134. package/dist/routes/_internal/public-archive-route.js +8 -3
  135. package/dist/routes/_internal/public-item-route.cc.d.ts +5 -1
  136. package/dist/routes/_internal/public-item-route.cc.d.ts.map +1 -1
  137. package/dist/routes/_internal/public-item-route.cc.js +2 -1
  138. package/dist/routes/_internal/public-item-route.d.ts +7 -0
  139. package/dist/routes/_internal/public-item-route.d.ts.map +1 -1
  140. package/dist/routes/_internal/public-item-route.js +6 -1
  141. package/dist/routes/_internal/superadmin-layout.cc.d.ts +2 -1
  142. package/dist/routes/_internal/superadmin-layout.cc.d.ts.map +1 -1
  143. package/dist/routes/_internal/superadmin-layout.cc.js +1 -1
  144. package/dist/routes/_internal/superadmin-layout.d.ts +7 -2
  145. package/dist/routes/_internal/superadmin-layout.d.ts.map +1 -1
  146. package/dist/routes/_internal/superadmin-layout.js +6 -6
  147. package/dist/routes/_internal/suspended-route.d.ts +16 -0
  148. package/dist/routes/_internal/suspended-route.d.ts.map +1 -0
  149. package/dist/routes/_internal/suspended-route.js +7 -0
  150. package/dist/routes/api/auth/[...all]/route.d.ts.map +1 -1
  151. package/dist/routes/api/auth/[...all]/route.js +4 -12
  152. package/dist/routes/api/csp-report/route.d.ts.map +1 -1
  153. package/dist/routes/api/csp-report/route.js +2 -13
  154. package/dist/routes/api/devtools/config/entities/route.d.ts +2 -0
  155. package/dist/routes/api/devtools/config/entities/route.d.ts.map +1 -1
  156. package/dist/routes/api/devtools/config/entities/route.js +2 -0
  157. package/dist/routes/api/devtools/config/theme/route.d.ts +2 -0
  158. package/dist/routes/api/devtools/config/theme/route.d.ts.map +1 -1
  159. package/dist/routes/api/devtools/config/theme/route.js +2 -0
  160. package/dist/routes/api/devtools/tests/[...path]/route.d.ts +2 -0
  161. package/dist/routes/api/devtools/tests/[...path]/route.d.ts.map +1 -1
  162. package/dist/routes/api/devtools/tests/[...path]/route.js +2 -0
  163. package/dist/routes/api/devtools/tests/route.d.ts +2 -0
  164. package/dist/routes/api/devtools/tests/route.d.ts.map +1 -1
  165. package/dist/routes/api/devtools/tests/route.js +2 -0
  166. package/dist/routes/api/health/route.d.ts +2 -0
  167. package/dist/routes/api/health/route.d.ts.map +1 -1
  168. package/dist/routes/api/health/route.js +2 -0
  169. package/dist/routes/api/internal/user-metadata/route.d.ts +2 -0
  170. package/dist/routes/api/internal/user-metadata/route.d.ts.map +1 -1
  171. package/dist/routes/api/internal/user-metadata/route.js +2 -0
  172. package/dist/routes/api/mcp/route.d.ts +2 -0
  173. package/dist/routes/api/mcp/route.d.ts.map +1 -1
  174. package/dist/routes/api/mcp/route.js +2 -0
  175. package/dist/routes/api/superadmin/subscriptions/route.d.ts +2 -0
  176. package/dist/routes/api/superadmin/subscriptions/route.d.ts.map +1 -1
  177. package/dist/routes/api/superadmin/subscriptions/route.js +2 -0
  178. package/dist/routes/api/superadmin/teams/[teamId]/route.d.ts +2 -0
  179. package/dist/routes/api/superadmin/teams/[teamId]/route.d.ts.map +1 -1
  180. package/dist/routes/api/superadmin/teams/[teamId]/route.js +2 -0
  181. package/dist/routes/api/superadmin/teams/route.d.ts +2 -0
  182. package/dist/routes/api/superadmin/teams/route.d.ts.map +1 -1
  183. package/dist/routes/api/superadmin/teams/route.js +2 -0
  184. package/dist/routes/api/superadmin/users/[userId]/route.d.ts +2 -0
  185. package/dist/routes/api/superadmin/users/[userId]/route.d.ts.map +1 -1
  186. package/dist/routes/api/superadmin/users/[userId]/route.js +2 -0
  187. package/dist/routes/api/superadmin/users/route.d.ts +2 -0
  188. package/dist/routes/api/superadmin/users/route.d.ts.map +1 -1
  189. package/dist/routes/api/superadmin/users/route.js +2 -0
  190. package/dist/routes/api/user/delete-account/route.d.ts +2 -0
  191. package/dist/routes/api/user/delete-account/route.d.ts.map +1 -1
  192. package/dist/routes/api/user/delete-account/route.js +2 -0
  193. package/dist/routes/api/user/plan-flags/route.d.ts +2 -0
  194. package/dist/routes/api/user/plan-flags/route.d.ts.map +1 -1
  195. package/dist/routes/api/user/plan-flags/route.js +2 -0
  196. package/dist/routes/api/user/profile/route.d.ts +2 -0
  197. package/dist/routes/api/user/profile/route.d.ts.map +1 -1
  198. package/dist/routes/api/user/profile/route.js +4 -3
  199. package/dist/routes/api/v1/[entity]/[id]/child/[childType]/[childId]/route.d.ts +2 -0
  200. package/dist/routes/api/v1/[entity]/[id]/child/[childType]/[childId]/route.d.ts.map +1 -1
  201. package/dist/routes/api/v1/[entity]/[id]/child/[childType]/[childId]/route.js +2 -0
  202. package/dist/routes/api/v1/[entity]/[id]/child/[childType]/route.d.ts +2 -0
  203. package/dist/routes/api/v1/[entity]/[id]/child/[childType]/route.d.ts.map +1 -1
  204. package/dist/routes/api/v1/[entity]/[id]/child/[childType]/route.js +2 -0
  205. package/dist/routes/api/v1/billing/cancel/route.d.ts +2 -0
  206. package/dist/routes/api/v1/billing/cancel/route.d.ts.map +1 -1
  207. package/dist/routes/api/v1/billing/cancel/route.js +2 -0
  208. package/dist/routes/api/v1/billing/change-plan/route.d.ts +2 -0
  209. package/dist/routes/api/v1/billing/change-plan/route.d.ts.map +1 -1
  210. package/dist/routes/api/v1/billing/change-plan/route.js +2 -0
  211. package/dist/routes/api/v1/billing/check-action/route.d.ts +2 -0
  212. package/dist/routes/api/v1/billing/check-action/route.d.ts.map +1 -1
  213. package/dist/routes/api/v1/billing/check-action/route.js +2 -0
  214. package/dist/routes/api/v1/billing/checkout/route.d.ts +2 -0
  215. package/dist/routes/api/v1/billing/checkout/route.d.ts.map +1 -1
  216. package/dist/routes/api/v1/billing/checkout/route.js +2 -0
  217. package/dist/routes/api/v1/billing/plans/route.d.ts +2 -0
  218. package/dist/routes/api/v1/billing/plans/route.d.ts.map +1 -1
  219. package/dist/routes/api/v1/billing/plans/route.js +2 -0
  220. package/dist/routes/api/v1/billing/portal/route.d.ts +2 -0
  221. package/dist/routes/api/v1/billing/portal/route.d.ts.map +1 -1
  222. package/dist/routes/api/v1/billing/portal/route.js +2 -0
  223. package/dist/routes/api/v1/blocks/[slug]/route.d.ts +2 -0
  224. package/dist/routes/api/v1/blocks/[slug]/route.d.ts.map +1 -1
  225. package/dist/routes/api/v1/blocks/[slug]/route.js +2 -0
  226. package/dist/routes/api/v1/blocks/route.d.ts +2 -0
  227. package/dist/routes/api/v1/blocks/route.d.ts.map +1 -1
  228. package/dist/routes/api/v1/blocks/route.js +2 -0
  229. package/dist/routes/api/v1/blocks/validate/route.d.ts +2 -0
  230. package/dist/routes/api/v1/blocks/validate/route.d.ts.map +1 -1
  231. package/dist/routes/api/v1/blocks/validate/route.js +2 -0
  232. package/dist/routes/api/v1/devtools/blocks/route.d.ts +2 -1
  233. package/dist/routes/api/v1/devtools/blocks/route.d.ts.map +1 -1
  234. package/dist/routes/api/v1/devtools/blocks/route.js +2 -9
  235. package/dist/routes/api/v1/devtools/docs/route.d.ts +2 -0
  236. package/dist/routes/api/v1/devtools/docs/route.d.ts.map +1 -1
  237. package/dist/routes/api/v1/devtools/docs/route.js +2 -0
  238. package/dist/routes/api/v1/devtools/features/route.d.ts +2 -1
  239. package/dist/routes/api/v1/devtools/features/route.d.ts.map +1 -1
  240. package/dist/routes/api/v1/devtools/features/route.js +2 -9
  241. package/dist/routes/api/v1/devtools/flows/route.d.ts +2 -1
  242. package/dist/routes/api/v1/devtools/flows/route.d.ts.map +1 -1
  243. package/dist/routes/api/v1/devtools/flows/route.js +2 -9
  244. package/dist/routes/api/v1/devtools/scheduled-actions/route.d.ts +2 -1
  245. package/dist/routes/api/v1/devtools/scheduled-actions/route.d.ts.map +1 -1
  246. package/dist/routes/api/v1/devtools/scheduled-actions/route.js +2 -9
  247. package/dist/routes/api/v1/devtools/scheduled-actions/run/route.d.ts +2 -1
  248. package/dist/routes/api/v1/devtools/scheduled-actions/run/route.d.ts.map +1 -1
  249. package/dist/routes/api/v1/devtools/scheduled-actions/run/route.js +2 -9
  250. package/dist/routes/api/v1/devtools/testing/route.d.ts +2 -1
  251. package/dist/routes/api/v1/devtools/testing/route.d.ts.map +1 -1
  252. package/dist/routes/api/v1/devtools/testing/route.js +2 -9
  253. package/dist/routes/api/v1/media/[id]/route.d.ts +2 -0
  254. package/dist/routes/api/v1/media/[id]/route.d.ts.map +1 -1
  255. package/dist/routes/api/v1/media/[id]/route.js +2 -0
  256. package/dist/routes/api/v1/media/[id]/tags/route.d.ts +2 -0
  257. package/dist/routes/api/v1/media/[id]/tags/route.d.ts.map +1 -1
  258. package/dist/routes/api/v1/media/[id]/tags/route.js +2 -0
  259. package/dist/routes/api/v1/media/check-duplicates/route.d.ts +2 -0
  260. package/dist/routes/api/v1/media/check-duplicates/route.d.ts.map +1 -1
  261. package/dist/routes/api/v1/media/check-duplicates/route.js +2 -0
  262. package/dist/routes/api/v1/media/route.d.ts +2 -0
  263. package/dist/routes/api/v1/media/route.d.ts.map +1 -1
  264. package/dist/routes/api/v1/media/route.js +2 -0
  265. package/dist/routes/api/v1/media/upload/route.d.ts +2 -0
  266. package/dist/routes/api/v1/media/upload/route.d.ts.map +1 -1
  267. package/dist/routes/api/v1/media/upload/route.js +2 -0
  268. package/dist/routes/api/v1/media-tags/route.d.ts +2 -0
  269. package/dist/routes/api/v1/media-tags/route.d.ts.map +1 -1
  270. package/dist/routes/api/v1/media-tags/route.js +2 -0
  271. package/dist/routes/api/v1/post-categories/[id]/route.d.ts +2 -0
  272. package/dist/routes/api/v1/post-categories/[id]/route.d.ts.map +1 -1
  273. package/dist/routes/api/v1/post-categories/[id]/route.js +2 -0
  274. package/dist/routes/api/v1/post-categories/route.d.ts +2 -0
  275. package/dist/routes/api/v1/post-categories/route.d.ts.map +1 -1
  276. package/dist/routes/api/v1/post-categories/route.js +2 -0
  277. package/dist/routes/api/v1/teams/[teamId]/members/route.js +2 -2
  278. package/dist/routes/api/v1/teams/[teamId]/subscription/route.d.ts +2 -0
  279. package/dist/routes/api/v1/teams/[teamId]/subscription/route.d.ts.map +1 -1
  280. package/dist/routes/api/v1/teams/[teamId]/subscription/route.js +2 -0
  281. package/dist/routes/api/v1/teams/[teamId]/usage/[limitSlug]/route.d.ts +2 -0
  282. package/dist/routes/api/v1/teams/[teamId]/usage/[limitSlug]/route.d.ts.map +1 -1
  283. package/dist/routes/api/v1/teams/[teamId]/usage/[limitSlug]/route.js +2 -0
  284. package/dist/routes/api/v1/users/[id]/route.d.ts.map +1 -1
  285. package/dist/routes/api/v1/users/[id]/route.js +3 -2
  286. package/dist/routes/api/v1/users/docs.md +34 -19
  287. package/dist/routes/api/v1/users/me/route.d.ts +6 -0
  288. package/dist/routes/api/v1/users/me/route.d.ts.map +1 -0
  289. package/dist/routes/api/v1/users/me/route.js +5 -0
  290. package/dist/routes/api/v1/users/presets.ts +2 -2
  291. package/dist/routes/dashboard/(main)/layout.cc.d.ts +4 -0
  292. package/dist/routes/dashboard/(main)/layout.cc.d.ts.map +1 -0
  293. package/dist/routes/dashboard/(main)/layout.cc.js +10 -0
  294. package/dist/routes/dashboard/(main)/layout.d.ts.map +1 -1
  295. package/dist/routes/dashboard/(main)/layout.js +3 -44
  296. package/dist/routes/dashboard/permission-denied/page.d.ts +1 -1
  297. package/dist/routes/dashboard/permission-denied/page.d.ts.map +1 -1
  298. package/dist/routes/dashboard/permission-denied/page.js +5 -1
  299. package/dist/routes/devtools/layout.cc.d.ts.map +1 -1
  300. package/dist/routes/devtools/layout.cc.js +5 -1
  301. package/dist/routes/manifest.json +10 -0
  302. package/dist/routes/robots.d.ts +10 -0
  303. package/dist/routes/robots.d.ts.map +1 -0
  304. package/dist/routes/robots.js +10 -0
  305. package/dist/routes/superadmin/layout.cc.d.ts.map +1 -1
  306. package/dist/routes/superadmin/layout.cc.js +5 -1
  307. package/dist/routes/variants.json +12 -2
  308. package/dist/templates/next.config.mjs +2 -29
  309. package/dist/templates/projects/blog/api/authors/[username]/route.ts +3 -0
  310. package/dist/templates/projects/blog/api/authors/route.ts +3 -0
  311. package/dist/templates/projects/blog/api/posts/public/route.ts +3 -0
  312. package/dist/templates/proxy.ts +12 -1
  313. package/dist/templates/public/favicon.ico +0 -0
  314. package/package.json +2 -2
  315. package/scripts/build/registry/__tests__/api-docs-colocated.test.mjs +3 -0
  316. package/scripts/build/registry/__tests__/billing-registry.test.mjs +50 -1
  317. package/scripts/build/registry/__tests__/block-registry.test.mjs +3 -0
  318. package/scripts/build/registry/__tests__/icons-discovery.test.mjs +3 -0
  319. package/scripts/build/registry/__tests__/quiet-console.mjs +17 -0
  320. package/scripts/build/registry/__tests__/safe-fs.test.mjs +3 -0
  321. package/scripts/build/registry/__tests__/team-roles.test.mjs +3 -0
  322. package/scripts/build/registry/discovery/permissions.test.mjs +3 -0
  323. package/scripts/build/registry/discovery/plugin-capabilities.mjs +2 -2
  324. package/scripts/build/registry/generators/billing-registry.mjs +7 -3
  325. package/scripts/build/registry/host/README.md +25 -1
  326. package/scripts/build/registry/host/__tests__/host-area-access.test.mjs +32 -0
  327. package/scripts/build/registry/host/__tests__/host-entities.test.mjs +32 -1
  328. package/scripts/build/registry/host/__tests__/host-plan.test.mjs +12 -2
  329. package/scripts/build/registry/host/entity-routes.mjs +9 -3
  330. package/scripts/build/registry/host/static-imports.mjs +4 -0
  331. package/scripts/build/routes-manifest.mjs +13 -1
  332. package/scripts/db/ssl-config.mjs +12 -1
  333. package/templates/next.config.mjs +2 -29
  334. package/templates/projects/blog/api/authors/[username]/route.ts +3 -0
  335. package/templates/projects/blog/api/authors/route.ts +3 -0
  336. package/templates/projects/blog/api/posts/public/route.ts +3 -0
  337. package/templates/proxy.ts +12 -1
  338. package/templates/public/favicon.ico +0 -0
@@ -0,0 +1 @@
1
+ {"version":3,"file":"layout.cc.d.ts","sourceRoot":"","sources":["../../../../src/routes/dashboard/(main)/layout.cc.tsx"],"names":[],"mappings":"AAgBA,MAAM,CAAC,OAAO,UAAU,mBAAmB,CAAC,EAAE,QAAQ,EAAE,EAAE;IAAE,QAAQ,EAAE,KAAK,CAAC,SAAS,CAAA;CAAE,2CAQtF"}
@@ -0,0 +1,10 @@
1
+ import { Fragment, jsx } from "react/jsx-runtime";
2
+ import { Suspense } from "react";
3
+ import { MainDashboardShell, enforceEntityPermission } from "../../_internal/dashboard-main-shared.js";
4
+ async function EntityPermission({ children }) {
5
+ await enforceEntityPermission();
6
+ return /* @__PURE__ */ jsx(Fragment, { children });
7
+ }
8
+ export default function MainDashboardLayout({ children }) {
9
+ return /* @__PURE__ */ jsx(MainDashboardShell, { children: /* @__PURE__ */ jsx(Suspense, { fallback: null, children: /* @__PURE__ */ jsx(EntityPermission, { children }) }) });
10
+ }
@@ -1 +1 @@
1
- {"version":3,"file":"layout.d.ts","sourceRoot":"","sources":["../../../../src/routes/dashboard/(main)/layout.tsx"],"names":[],"mappings":"AAsDA,iBAAe,0BAA0B,CAAC,EACxC,QAAQ,EACT,EAAE;IACD,QAAQ,EAAE,KAAK,CAAC,SAAS,CAAA;CAC1B,oDAmCA;AAGD,eAAe,0BAA0B,CAAA"}
1
+ {"version":3,"file":"layout.d.ts","sourceRoot":"","sources":["../../../../src/routes/dashboard/(main)/layout.tsx"],"names":[],"mappings":"AAcA,iBAAe,0BAA0B,CAAC,EACxC,QAAQ,EACT,EAAE;IACD,QAAQ,EAAE,KAAK,CAAC,SAAS,CAAA;CAC1B,oDAIA;AAGD,eAAe,0BAA0B,CAAA"}
@@ -1,50 +1,9 @@
1
1
  import { jsx } from "react/jsx-runtime";
2
- import { headers } from "next/headers";
3
- import { redirect } from "next/navigation";
4
- import { DashboardShell } from "@nextsparkjs/core/components/dashboard/layouts/DashboardShell";
5
- import { serializeEntityConfig } from "@nextsparkjs/core/lib/entities/serialization";
6
- import { setEntityRegistry } from "@nextsparkjs/core/lib/entities/queries";
7
- import { checkPermission } from "@nextsparkjs/core/lib/permissions/check";
8
- import { getDashboardPermissionContext } from "@nextsparkjs/core/lib/auth/request-session";
9
- import { isValidPermission } from "@nextsparkjs/core/lib/permissions/init";
10
- import { ENTITY_REGISTRY, ENTITY_METADATA } from "@nextsparkjs/registries/entity-registry";
11
- setEntityRegistry(ENTITY_REGISTRY, ENTITY_METADATA);
12
- function parseEntityFromPathname(pathname) {
13
- const match = pathname.match(/^\/dashboard\/([^/]+)(?:\/(.*))?$/);
14
- if (!match) return null;
15
- const entity = match[1];
16
- const rest = match[2] || "";
17
- if (rest === "" || rest === "/") return { entity, action: "list" };
18
- if (rest === "create") return { entity, action: "create" };
19
- if (rest.match(/^[^/]+\/edit$/)) return { entity, action: "update" };
20
- if (rest.match(/^[^/]+$/)) return { entity, action: "read" };
21
- return { entity, action: "read" };
22
- }
23
- function isEntityConfig(entity) {
24
- return "slug" in entity;
25
- }
2
+ import { MainDashboardShell, enforceEntityPermission } from "../../_internal/dashboard-main-shared.js";
26
3
  async function DefaultMainDashboardLayout({
27
4
  children
28
5
  }) {
29
- const allEntities = Object.values(ENTITY_REGISTRY).map((entry) => entry.config);
30
- const entities = allEntities.filter(isEntityConfig);
31
- const serializedEntities = entities.map(serializeEntityConfig);
32
- const headersList = await headers();
33
- const pathname = headersList.get("x-pathname") || "";
34
- const { userId, teamId } = await getDashboardPermissionContext();
35
- if (userId && teamId && pathname) {
36
- const parsed = parseEntityFromPathname(pathname);
37
- if (parsed) {
38
- const { entity, action } = parsed;
39
- const permission = `${entity}.${action}`;
40
- if (isValidPermission(`${entity}.list`)) {
41
- const hasPermission = await checkPermission(userId, teamId, permission);
42
- if (!hasPermission) {
43
- redirect(`/dashboard/permission-denied?entity=${entity}&action=${action}`);
44
- }
45
- }
46
- }
47
- }
48
- return /* @__PURE__ */ jsx(DashboardShell, { entities: serializedEntities, children });
6
+ await enforceEntityPermission();
7
+ return /* @__PURE__ */ jsx(MainDashboardShell, { children });
49
8
  }
50
9
  export default DefaultMainDashboardLayout;
@@ -4,6 +4,6 @@ interface PermissionDeniedPageProps {
4
4
  action?: string;
5
5
  }>;
6
6
  }
7
- declare function PermissionDeniedPage({ searchParams }: PermissionDeniedPageProps): Promise<import("react/jsx-runtime").JSX.Element>;
7
+ declare function PermissionDeniedPage({ searchParams }: PermissionDeniedPageProps): import("react/jsx-runtime").JSX.Element;
8
8
  export default PermissionDeniedPage;
9
9
  //# sourceMappingURL=page.d.ts.map
@@ -1 +1 @@
1
- {"version":3,"file":"page.d.ts","sourceRoot":"","sources":["../../../../src/routes/dashboard/permission-denied/page.tsx"],"names":[],"mappings":"AAQA,UAAU,yBAAyB;IACjC,YAAY,EAAE,OAAO,CAAC;QACpB,MAAM,CAAC,EAAE,MAAM,CAAA;QACf,MAAM,CAAC,EAAE,MAAM,CAAA;KAChB,CAAC,CAAA;CACH;AAED,iBAAe,oBAAoB,CAAC,EAClC,YAAY,EACb,EAAE,yBAAyB,oDAW3B;AAED,eAAe,oBAAoB,CAAA"}
1
+ {"version":3,"file":"page.d.ts","sourceRoot":"","sources":["../../../../src/routes/dashboard/permission-denied/page.tsx"],"names":[],"mappings":"AASA,UAAU,yBAAyB;IACjC,YAAY,EAAE,OAAO,CAAC;QACpB,MAAM,CAAC,EAAE,MAAM,CAAA;QACf,MAAM,CAAC,EAAE,MAAM,CAAA;KAChB,CAAC,CAAA;CACH;AAkBD,iBAAS,oBAAoB,CAAC,EAAE,YAAY,EAAE,EAAE,yBAAyB,2CAMxE;AAED,eAAe,oBAAoB,CAAA"}
@@ -1,6 +1,7 @@
1
1
  import { jsx } from "react/jsx-runtime";
2
+ import { Suspense } from "react";
2
3
  import { NoPermission } from "@nextsparkjs/core/components/permissions/NoPermission";
3
- async function PermissionDeniedPage({
4
+ async function DeniedMessage({
4
5
  searchParams
5
6
  }) {
6
7
  const { entity, action } = await searchParams;
@@ -14,4 +15,7 @@ async function PermissionDeniedPage({
14
15
  }
15
16
  );
16
17
  }
18
+ function PermissionDeniedPage({ searchParams }) {
19
+ return /* @__PURE__ */ jsx(Suspense, { fallback: null, children: /* @__PURE__ */ jsx(DeniedMessage, { searchParams }) });
20
+ }
17
21
  export default PermissionDeniedPage;
@@ -1 +1 @@
1
- {"version":3,"file":"layout.cc.d.ts","sourceRoot":"","sources":["../../../src/routes/devtools/layout.cc.tsx"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,QAAQ,EAAE,MAAM,MAAM,CAAA;AAIpC,eAAO,MAAM,QAAQ,EAAE,QAA0B,CAAA;;;;AAEjD,wBAAkD"}
1
+ {"version":3,"file":"layout.cc.d.ts","sourceRoot":"","sources":["../../../src/routes/devtools/layout.cc.tsx"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,QAAQ,EAAE,MAAM,MAAM,CAAA;AAIpC,eAAO,MAAM,QAAQ,EAAE,QAA0B,CAAA;;;;AAUjD,wBAA+D"}
@@ -1,4 +1,8 @@
1
+ import { jsx } from "react/jsx-runtime";
1
2
  import { DevLayout, defaultMetadata } from "../_internal/devtools-layout.js";
2
3
  import { withDevtoolsAreaMessages } from "../_internal/devtools-layout.cc.js";
3
4
  export const metadata = defaultMetadata;
4
- export default withDevtoolsAreaMessages(DevLayout);
5
+ function ServerCheckedDevLayout({ children }) {
6
+ return /* @__PURE__ */ jsx(DevLayout, { serverChecked: true, children });
7
+ }
8
+ export default withDevtoolsAreaMessages(ServerCheckedDevLayout);
@@ -422,6 +422,11 @@
422
422
  "target": "api/v1/users/[id]/route.ts",
423
423
  "specifier": "@nextsparkjs/core/routes/api/v1/users/[id]/route"
424
424
  },
425
+ {
426
+ "kind": "route",
427
+ "target": "api/v1/users/me/route.ts",
428
+ "specifier": "@nextsparkjs/core/routes/api/v1/users/me/route"
429
+ },
425
430
  {
426
431
  "kind": "route",
427
432
  "target": "api/v1/users/route.ts",
@@ -716,6 +721,11 @@
716
721
  "target": "public/page.tsx",
717
722
  "specifier": "@nextsparkjs/core/routes/public/page"
718
723
  },
724
+ {
725
+ "kind": "robots",
726
+ "target": "robots.ts",
727
+ "specifier": "@nextsparkjs/core/routes/robots"
728
+ },
719
729
  {
720
730
  "kind": "page",
721
731
  "target": "superadmin/docs/[section]/[page]/page.tsx",
@@ -0,0 +1,10 @@
1
+ import type { MetadataRoute } from 'next';
2
+ /**
3
+ * /robots.txt (#214): public pages are crawlable, the authenticated areas and the API are not.
4
+ *
5
+ * Answers `text/plain` 200 in Cache Components and legacy ISR alike, so a crawler never gets the public
6
+ * catch-all page's HTML. A project overrides it with its own `robots.ts` (templates/robots.ts), which
7
+ * replaces this route. Core has no sitemap, so none is declared.
8
+ */
9
+ export default function robots(): MetadataRoute.Robots;
10
+ //# sourceMappingURL=robots.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"robots.d.ts","sourceRoot":"","sources":["../../src/routes/robots.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,aAAa,EAAE,MAAM,MAAM,CAAA;AAGzC;;;;;;GAMG;AACH,MAAM,CAAC,OAAO,UAAU,MAAM,IAAI,aAAa,CAAC,MAAM,CAQrD"}
@@ -0,0 +1,10 @@
1
+ import { withBasePath } from "@nextsparkjs/core/lib/base-path";
2
+ export default function robots() {
3
+ return {
4
+ rules: {
5
+ userAgent: "*",
6
+ allow: withBasePath("/"),
7
+ disallow: ["/dashboard", "/superadmin", "/devtools", "/api"].map(withBasePath)
8
+ }
9
+ };
10
+ }
@@ -1 +1 @@
1
- {"version":3,"file":"layout.cc.d.ts","sourceRoot":"","sources":["../../../src/routes/superadmin/layout.cc.tsx"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,QAAQ,EAAE,MAAM,MAAM,CAAA;AAIpC,eAAO,MAAM,QAAQ,EAAE,QAA0B,CAAA;;;;AAEjD,wBAA2D"}
1
+ {"version":3,"file":"layout.cc.d.ts","sourceRoot":"","sources":["../../../src/routes/superadmin/layout.cc.tsx"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,QAAQ,EAAE,MAAM,MAAM,CAAA;AAIpC,eAAO,MAAM,QAAQ,EAAE,QAA0B,CAAA;;;;AAUjD,wBAAwE"}
@@ -1,4 +1,8 @@
1
+ import { jsx } from "react/jsx-runtime";
1
2
  import { SuperadminLayout, defaultMetadata } from "../_internal/superadmin-layout.js";
2
3
  import { withSuperadminAreaMessages } from "../_internal/superadmin-layout.cc.js";
3
4
  export const metadata = defaultMetadata;
4
- export default withSuperadminAreaMessages(SuperadminLayout);
5
+ function ServerCheckedSuperadminLayout({ children }) {
6
+ return /* @__PURE__ */ jsx(SuperadminLayout, { serverChecked: true, children });
7
+ }
8
+ export default withSuperadminAreaMessages(ServerCheckedSuperadminLayout);
@@ -39,7 +39,7 @@
39
39
  "wrapper": "withSuperadminAccess",
40
40
  "metadata": "withSuperadminMetadata",
41
41
  "handler": "withSuperadminRouteAccess",
42
- "specifier": "@nextsparkjs/core/routes/_internal/area-access"
42
+ "specifier": "@nextsparkjs/core/routes/_internal/area-access.cc"
43
43
  }
44
44
  },
45
45
  {
@@ -54,7 +54,7 @@
54
54
  "wrapper": "withDevtoolsAccess",
55
55
  "metadata": "withDevtoolsMetadata",
56
56
  "handler": "withDevtoolsRouteAccess",
57
- "specifier": "@nextsparkjs/core/routes/_internal/area-access"
57
+ "specifier": "@nextsparkjs/core/routes/_internal/area-access.cc"
58
58
  }
59
59
  },
60
60
  {
@@ -62,6 +62,16 @@
62
62
  "target": "dashboard/layout.tsx",
63
63
  "specifier": "@nextsparkjs/core/routes/dashboard/layout.cc"
64
64
  },
65
+ {
66
+ "kind": "layout",
67
+ "target": "dashboard/(main)/layout.tsx",
68
+ "specifier": "@nextsparkjs/core/routes/dashboard/(main)/layout.cc"
69
+ },
70
+ {
71
+ "kind": "page",
72
+ "target": "(public)/page.tsx",
73
+ "specifier": "@nextsparkjs/core/routes/(public)/page.cc"
74
+ },
65
75
  {
66
76
  "kind": "page",
67
77
  "target": "(auth)/login/page.tsx",
@@ -260,35 +260,8 @@ const nextConfig = {
260
260
  source: '/:path*',
261
261
  headers: securityHeaders
262
262
  },
263
- // CORS headers for API routes
264
- // NOTE: In development, CORS is handled dynamically by API routes using addCorsHeaders()
265
- // to support multiple origins (web app, mobile app, etc.)
266
- // In production, we set static CORS headers here
267
- ...(isProduction ? [{
268
- source: '/api/:path*',
269
- headers: [
270
- {
271
- key: 'Access-Control-Allow-Origin',
272
- value: process.env.NEXT_PUBLIC_APP_URL || 'http://localhost:3000'
273
- },
274
- {
275
- key: 'Access-Control-Allow-Methods',
276
- value: 'GET, POST, PUT, DELETE, OPTIONS, PATCH'
277
- },
278
- {
279
- key: 'Access-Control-Allow-Headers',
280
- value: 'Content-Type, Authorization, x-api-key, x-verify-from-ui, Cookie, Set-Cookie'
281
- },
282
- {
283
- key: 'Access-Control-Allow-Credentials',
284
- value: 'true'
285
- },
286
- {
287
- key: 'Access-Control-Expose-Headers',
288
- value: 'Set-Cookie'
289
- }
290
- ]
291
- }] : [])
263
+ // No CORS headers here: core's API routes answer CORS per request (addCorsHeaders), so the
264
+ // origins in api.cors.allowedOrigins and CORS_ADDITIONAL_ORIGINS get their grant in production too
292
265
  ]
293
266
  },
294
267
  }
@@ -10,6 +10,7 @@
10
10
  import { NextRequest, NextResponse } from 'next/server'
11
11
  import { queryWithRLS } from '@nextsparkjs/core/lib/db'
12
12
  import { withRateLimitTier } from '@nextsparkjs/core/lib/api/rate-limit'
13
+ import { corsPreflight } from '@nextsparkjs/core/lib/api/cors-response'
13
14
 
14
15
  interface Author {
15
16
  id: string
@@ -151,3 +152,5 @@ const getHandler = async (
151
152
  }
152
153
 
153
154
  export const GET = withRateLimitTier(getHandler, 'read')
155
+
156
+ export const OPTIONS = corsPreflight
@@ -10,6 +10,7 @@
10
10
  import { NextRequest, NextResponse } from 'next/server'
11
11
  import { queryWithRLS } from '@nextsparkjs/core/lib/db'
12
12
  import { withRateLimitTier } from '@nextsparkjs/core/lib/api/rate-limit'
13
+ import { corsPreflight } from '@nextsparkjs/core/lib/api/cors-response'
13
14
 
14
15
  interface AuthorWithCount {
15
16
  id: string
@@ -64,3 +65,5 @@ const getHandler = async (request: NextRequest) => {
64
65
  }
65
66
 
66
67
  export const GET = withRateLimitTier(getHandler, 'read')
68
+
69
+ export const OPTIONS = corsPreflight
@@ -11,6 +11,7 @@
11
11
  import { NextRequest, NextResponse } from 'next/server'
12
12
  import { queryWithRLS } from '@nextsparkjs/core/lib/db'
13
13
  import { withRateLimitTier } from '@nextsparkjs/core/lib/api/rate-limit'
14
+ import { corsPreflight } from '@nextsparkjs/core/lib/api/cors-response'
14
15
 
15
16
  interface Post {
16
17
  id: string
@@ -152,3 +153,5 @@ const getHandler = async (request: NextRequest) => {
152
153
  }
153
154
 
154
155
  export const GET = withRateLimitTier(getHandler, 'read')
156
+
157
+ export const OPTIONS = corsPreflight
@@ -200,8 +200,19 @@ function getSession(request: NextRequest) {
200
200
  * session. Gating the strip on protected prefixes is not enough: Next.js
201
201
  * dispatches Server Actions by the `Next-Action` header, not by the URL, so an
202
202
  * action can be POSTed to a public path with a forged `x-user-id`.
203
+ *
204
+ * The `x-api-*` names are never set here: core takes an API key's identity from
205
+ * validating the key, and nothing downstream may receive them from the client.
203
206
  */
204
- const TRUSTED_IDENTITY_HEADERS = ['x-user-id', 'x-user-email', 'x-pathname', 'x-active-team-id'] as const
207
+ const TRUSTED_IDENTITY_HEADERS = [
208
+ 'x-user-id',
209
+ 'x-user-email',
210
+ 'x-pathname',
211
+ 'x-active-team-id',
212
+ 'x-api-user-id',
213
+ 'x-api-key-id',
214
+ 'x-api-scopes',
215
+ ] as const
205
216
 
206
217
  /**
207
218
  * Build the request headers forwarded to the app: inbound copy minus every
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@nextsparkjs/core",
3
- "version": "0.1.0-beta.194",
3
+ "version": "0.1.0-beta.195",
4
4
  "description": "NextSpark - The complete SaaS framework for Next.js",
5
5
  "license": "MIT",
6
6
  "author": "NextSpark <hello@nextspark.dev>",
@@ -496,7 +496,7 @@
496
496
  "use-intl": "^4.9.1",
497
497
  "uuid": "^13.0.0",
498
498
  "zod": "^4.3.6",
499
- "@nextsparkjs/ui": "0.1.0-beta.194"
499
+ "@nextsparkjs/ui": "0.1.0-beta.195"
500
500
  },
501
501
  "engines": {
502
502
  "node": ">=22.14.0"
@@ -15,6 +15,9 @@ import test from 'node:test'
15
15
  import { discoverApiPresets } from '../discovery/api-presets.mjs'
16
16
  import { discoverRouteFiles } from '../discovery/plugins.mjs'
17
17
  import { generateRouteHandlersRegistry } from '../generators/route-handlers.mjs'
18
+ import { silenceConsoleOutput } from './quiet-console.mjs'
19
+
20
+ silenceConsoleOutput()
18
21
 
19
22
  const presets = endpoint => `export default defineApiEndpoint({\n endpoint: '${endpoint}',\n summary: 'S',\n presets: [{ id: 'a', title: 'A', method: 'GET' }]\n})\n`
20
23
 
@@ -8,12 +8,15 @@
8
8
 
9
9
  import { test } from 'node:test'
10
10
  import assert from 'node:assert/strict'
11
- import { mkdtemp, readdir, rm } from 'node:fs/promises'
11
+ import { mkdir, mkdtemp, readdir, rm, writeFile } from 'node:fs/promises'
12
12
  import { tmpdir } from 'node:os'
13
13
  import { dirname, join } from 'node:path'
14
14
  import { fileURLToPath } from 'node:url'
15
15
 
16
16
  import { generateBillingRegistry } from '../generators/billing-registry.mjs'
17
+ import { silenceConsoleOutput } from './quiet-console.mjs'
18
+
19
+ silenceConsoleOutput()
17
20
 
18
21
  const REPO_ROOT = join(dirname(fileURLToPath(import.meta.url)), '..', '..', '..', '..', '..', '..')
19
22
 
@@ -45,3 +48,49 @@ test("generateBillingRegistry's jiti import writes nothing under TMPDIR, even wh
45
48
  await cacheRoot.cleanup()
46
49
  }
47
50
  })
51
+
52
+ // #212: a billing config that exists but cannot load must fail the build, never become an empty registry
53
+ async function projectWith(billingSource) {
54
+ const dir = await directory('nextspark-billing-registry-cases-')
55
+ if (billingSource !== undefined) {
56
+ await mkdir(join(dir.root, 'config'), { recursive: true })
57
+ await writeFile(join(dir.root, 'config/billing.config.ts'), billingSource)
58
+ }
59
+ return { ...dir, config: { projectRoot: dir.root, projectSourceDir: dir.root, projectName: 'cases', outputDir: join(dir.root, 'out') } }
60
+ }
61
+
62
+ test('no billing config: the empty registry, the build keeps going', async () => {
63
+ const project = await projectWith(undefined)
64
+ try {
65
+ assert.match(await generateBillingRegistry(project.config), /No billing config found - empty registry generated/)
66
+ } finally { await project.cleanup() }
67
+ })
68
+
69
+ test('a valid billing config is read into the registry', async () => {
70
+ const project = await projectWith(`export const billingConfig = { provider: 'stripe', currency: 'usd', defaultPlan: 'free', features: { api: {} }, limits: { seats: {} }, plans: [{ slug: 'free', visibility: 'public', features: ['api'], limits: { seats: 3 } }] }\n`)
71
+ try {
72
+ const output = await generateBillingRegistry(project.config)
73
+ assert.match(output, /"totalPlans": 1/)
74
+ assert.match(output, /"seats": \{\s+"free": 3/)
75
+ } finally { await project.cleanup() }
76
+ })
77
+
78
+ test('a billing config that throws fails with its file name and the underlying error', async () => {
79
+ const project = await projectWith(`throw new Error('stripe price id is missing')\n`)
80
+ try {
81
+ await assert.rejects(generateBillingRegistry(project.config), (error) => {
82
+ assert.ok(error.message.includes(join(project.root, 'config/billing.config.ts')), error.message)
83
+ assert.ok(error.message.includes('stripe price id is missing'), error.message)
84
+ return true
85
+ })
86
+ } finally { await project.cleanup() }
87
+ })
88
+
89
+ test('a billing config with a syntax error or without a billingConfig export also fails', async () => {
90
+ for (const source of ['export const billingConfig = {', 'export const other = 1\n']) {
91
+ const project = await projectWith(source)
92
+ try {
93
+ await assert.rejects(generateBillingRegistry(project.config), /billing\.config\.ts/)
94
+ } finally { await project.cleanup() }
95
+ }
96
+ })
@@ -25,6 +25,9 @@ import {
25
25
  import { readSchemaExport, thumbnailImport } from '../discovery/blocks.mjs'
26
26
  import { validateGeneratedModule } from '../host/static-imports.mjs'
27
27
  import { loadTypeScriptFor } from '../shared/typescript-compiler.mjs'
28
+ import { silenceConsoleOutput } from './quiet-console.mjs'
29
+
30
+ silenceConsoleOutput()
28
31
 
29
32
  function block(slug, category) {
30
33
  const base = `@/blocks/${slug}`
@@ -24,6 +24,9 @@ import {
24
24
  isTestFilePath,
25
25
  extractLiteralIconCallNames
26
26
  } from '../discovery/icons.mjs'
27
+ import { silenceConsoleOutput } from './quiet-console.mjs'
28
+
29
+ silenceConsoleOutput()
27
30
 
28
31
  test('discovers core entity icons through the resolution owner coreDir', async () => {
29
32
  const root = await mkdtemp(join(tmpdir(), 'nextspark-icons-core-dir-'))
@@ -0,0 +1,17 @@
1
+ import { after, before, mock } from 'node:test'
2
+
3
+ /**
4
+ * Keeps a test file's in-process console.log/info output off the test child's stdout. The Node test runner
5
+ * reads that stdout for its own frames, and non-ASCII text on it (the build log's emoji) can make it fail with
6
+ * "Unable to deserialize cloned data" (nodejs/node#64061, fixed on 24+ only; #65934 for 22).
7
+ * Call once at the top of a file; a test that needs to read the output replaces console.log itself.
8
+ */
9
+ export function silenceConsoleOutput() {
10
+ before(() => {
11
+ mock.method(console, 'log', () => {})
12
+ mock.method(console, 'info', () => {})
13
+ })
14
+ after(() => {
15
+ mock.restoreAll()
16
+ })
17
+ }
@@ -23,6 +23,9 @@ import { fileURLToPath } from 'node:url'
23
23
  import { COPY_BLOCK_SIZE, isUnsafeWrite, projectFiles, UNSAFE_WRITE, unsafeWriteProblem } from '../../safe-fs.mjs'
24
24
  import { generateTestBlocksJson, generateTestEntitiesJson } from '../post-build/test-fixtures.mjs'
25
25
  import { directFsWrites, directFsWritesIn, generatorFiles } from './direct-fs-writes.mjs'
26
+ import { silenceConsoleOutput } from './quiet-console.mjs'
27
+
28
+ silenceConsoleOutput()
26
29
 
27
30
  const CORE_DIR = join(dirname(fileURLToPath(import.meta.url)), '..', '..', '..', '..')
28
31
  const RUNS_AS_ROOT = process.getuid?.() === 0
@@ -18,6 +18,9 @@ import assert from 'node:assert/strict'
18
18
 
19
19
  import { computeTeamRoleConfig, generatePermissionsRegistry } from '../generators/permissions-registry.mjs'
20
20
  import { parseTeamRolesFromAppConfig } from '../discovery/permissions.mjs'
21
+ import { silenceConsoleOutput } from './quiet-console.mjs'
22
+
23
+ silenceConsoleOutput()
21
24
 
22
25
  // ---------------------------------------------------------------------------
23
26
  // computeTeamRoleConfig — the pure build-time computation
@@ -6,6 +6,9 @@
6
6
 
7
7
  import { describe, it } from 'node:test'
8
8
  import assert from 'node:assert'
9
+ import { silenceConsoleOutput } from '../__tests__/quiet-console.mjs'
10
+
11
+ silenceConsoleOutput()
9
12
 
10
13
  // Import the regex pattern directly since parseEntitiesFromConfig is not exported
11
14
  // We'll test the regex patterns directly
@@ -329,11 +329,11 @@ const BUILTINS = new Set(builtinModules.filter(name => !name.startsWith('_')))
329
329
  /** Bare specifiers that only run on a server (`next/server` is Next's request/response and `after`/`connection` APIs). */
330
330
  const SERVER_ONLY_PACKAGES = new Set(['server-only', 'next/headers', 'next/server', 'pg', 'postgres', 'ioredis', 'nodemailer'])
331
331
  /**
332
- * The `lib/api` modules core's own 'use client' files import (the entity fetch helpers, `ApiError`, the scope catalog):
332
+ * The `lib/api` modules core's own 'use client' files import (the entity fetch helpers, `ApiError`, the scope catalog, the client request header names):
333
333
  * they import no server module, so they are client-safe. Everything else under `lib/api` is server-only. A guard test
334
334
  * checks that every `lib/api` module a core 'use client' file imports is in this list.
335
335
  */
336
- export const CLIENT_SAFE_CORE_API = ['entities', 'api-error', 'keys']
336
+ export const CLIENT_SAFE_CORE_API = ['entities', 'api-error', 'keys', 'client-headers']
337
337
  /** Core modules known to be server-only; the ones that import a marker above are also found by following the import. */
338
338
  const SERVER_ONLY_CORE = [
339
339
  /^@nextsparkjs\/core\/lib\/db(\/|$)/,
@@ -119,10 +119,14 @@ export async function generateBillingRegistry(configOrName, _legacyContentsDir,
119
119
  // Use jiti to import .ts files at build time (Node.js can't import .ts natively)
120
120
  const jiti = createJiti(import.meta.url, { interopDefault: true, fsCache: false })
121
121
  const module = await jiti.import(absolutePath)
122
- billingConfig = module.billingConfig || module.default
122
+ billingConfig = module.billingConfig
123
123
  } catch (error) {
124
- log(`Failed to import billing config from ${absolutePath}: ${error.message}`, 'error')
125
- return generateEmptyBillingRegistry(config)
124
+ // A config that exists but cannot load must stop the build: an empty registry has no plans,
125
+ // and a project without plans skips every feature and quota check.
126
+ throw new Error(`Failed to load billing config ${absolutePath}: ${error.message}`, { cause: error })
127
+ }
128
+ if (!billingConfig || !Array.isArray(billingConfig.plans) || !billingConfig.features || !billingConfig.limits) {
129
+ throw new Error(`Billing config ${absolutePath} must export \`billingConfig\` with plans, features and limits`)
126
130
  }
127
131
 
128
132
  // Pre-compute everything at build time
@@ -217,7 +217,8 @@ spread and no expression. A host with another core (the conformance fixture) pas
217
217
  `_internal/default-{auth,public}-layout`).
218
218
  - **Role-gated areas** `/superadmin`, `/devtools`: every page, layout, template and default served under the area's URL (core's,
219
219
  a plugin's or the project's, in any route group) is composed with `withSuperadminAccess` / `withDevtoolsAccess`
220
- (`routes/_internal/area-access`), which checks the session's role on the server before the segment renders, and its
220
+ (`routes/_internal/area-access`; `area-access.cc` in a Cache Components host, the same check inside the segment's own Suspense
221
+ boundary), which checks the session's role on the server before the segment renders, and its
221
222
  `generateMetadata` with `withSuperadminMetadata` / `withDevtoolsMetadata` (metadata resolves apart too); a Route Handler's
222
223
  methods but `OPTIONS` with `withSuperadminRouteAccess` / `withDevtoolsRouteAccess` (401 / 403 JSON; `OPTIONS` is forwarded
223
224
  unchecked, so it must not return data). An intercepting route
@@ -293,4 +294,27 @@ cookies or headers below no other boundary). The public item routes omit `revali
293
294
  mode only imports `_internal/public-item-route.cc`, whose reads of the published item and of the patterns it references are `'use cache'`
294
295
  functions (`cacheLife` of an hour for an item, tags `entity:<slug>` and `public-item:<slug>:<item slug>`).
295
296
 
297
+ **Instant navigation (dev server).** On a Cache Components host `next dev` runs Next.js's instant-navigation check on every page it
298
+ serves: it renders the route as a navigation into each segment and logs `Could not validate that a segment in your UI has instant
299
+ navigation` (a segment the server render never reached), `... uncached data during prerendering or a navigation` or `... runtime data
300
+ (`cookies()`, `headers()`, `params`, `searchParams`) accessed outside of <Suspense>`, and the dev badge shows an issue. The pages work; the check is
301
+ about what a navigation would wait for. What it found in the starter, and the fix, are in core, so no project file changes:
302
+ - The dashboard's client auth gate (`AuthenticatedDashboardLayout`) rendered a skeleton instead of `children` while the session loaded, so the
303
+ route's own segments were never rendered on the server ("dropped"). It renders the page while the session loads and keeps the signed-in side effects for
304
+ a known user.
305
+ - `dashboard/(main)/layout.cc` (one more `layout.cc.tsx` variant, for a layout whose permission check reads the request) renders the shell at once and runs the permission check (session and database) in a
306
+ Suspense boundary around the page; the ISR layout still checks before rendering anything, so a denied request keeps its real redirect.
307
+ - The pages that await `params` (the public item routes `public-item-route.cc`, the dashboard detail and edit routes `entity-detail-route.cc` /
308
+ `entity-edit-route.cc`, written for a host with `cacheComponents` on and nothing else) sit behind a boundary of their own
309
+ (`_internal/suspended-route`); the `/signup` variant and core's default home (`(public)/page.cc`) render the page that redirects behind one too, and the public archive and the permission-denied
310
+ page read `searchParams` behind one. The status a visitor gets does not change: the layouts already streamed these pages after their shell.
311
+ - A project without `public/favicon.ico` made the browser's own `GET /favicon.ico` hit the public `[slug]` page (an HTML "not found" with a 200);
312
+ new projects ship a favicon, and a request for a page that does not exist is now answered inside a boundary the check accepts.
313
+ - `/superadmin` and `/devtools` (#213): their client guards hid the page from the server render until the session loaded, and each segment awaited
314
+ its role check outside Suspense. In a Cache Components host the area layouts (`superadmin/layout.cc`, `devtools/layout.cc` and the composed
315
+ override through `withSuperadminGuard` / `withDevtoolsGuard`) pass `serverChecked` to their guard, which they render only after the role
316
+ check, and every segment under an area is composed from `_internal/area-access.cc`: the same check, inside the segment's own Suspense boundary,
317
+ the segment rendered only once it has passed. The ISR host keeps `_internal/area-access` and its guards' loading state.
318
+ Opting a segment out is `export const instant = false` in its source (a literal, as the facade rules require); nothing sets it today.
319
+
296
320
  Tests: `node --test packages/core/scripts/build/registry/host/__tests__/*.test.mjs`.
@@ -276,3 +276,35 @@ test('a metadata file under an area cannot be guarded: a warning notice names it
276
276
  w.cleanup()
277
277
  }
278
278
  })
279
+
280
+ /**
281
+ * #213: with core's own manifest, a Cache Components host composes every page and layout under the areas with
282
+ * area-access.cc (the check inside the segment's own Suspense boundary), a legacy ISR host with area-access as before.
283
+ * The facades stay inside the facade grammar either way.
284
+ */
285
+ test("core's manifest: area segments import their check from area-access.cc in a Cache Components host only", async () => {
286
+ const facades = async cacheComponents => {
287
+ const { routes } = await loadCoreRouteManifest({ coreRoot: CORE_ROOT, cacheComponents })
288
+ const planned = planHost({ coreRoutes: routes })
289
+ const result = await render(planned.routes)
290
+ assert.deepEqual(result.diagnostics, [])
291
+ return result
292
+ }
293
+ const INTERNAL = '@nextsparkjs/core/routes/_internal/'
294
+ for (const [cacheComponents, module] of [[true, 'area-access.cc'], [false, 'area-access']]) {
295
+ const result = await facades(cacheComponents)
296
+ for (const [target, wrapper] of [
297
+ ['superadmin/users/page.tsx', 'withSuperadminAccess'],
298
+ ['superadmin/docs/[section]/[page]/page.tsx', 'withSuperadminAccess'],
299
+ ['devtools/page.tsx', 'withDevtoolsAccess'],
300
+ ['devtools/api/layout.tsx', 'withDevtoolsAccess'],
301
+ ]) {
302
+ const content = contentOf(result, target)
303
+ assert.ok(content, target)
304
+ assert.ok(content.includes(`import { ${wrapper} } from "${INTERNAL}${module}"`), `${target} (cacheComponents ${cacheComponents}) imports ${wrapper} from ${module}:\n${content}`)
305
+ assert.deepEqual(validateGeneratedModule({ ts, source: content, file: `src/app/${target}`, grammar: 'composed-facade' }), [], target)
306
+ }
307
+ // A doc page's generateMetadata keeps area-access's check (re-exported by area-access.cc)
308
+ assert.match(contentOf(result, 'superadmin/docs/[section]/[page]/page.tsx'), new RegExp(`withSuperadminMetadata.*from "${INTERNAL}${module.replace('.', '\\.')}"`, 's'))
309
+ }
310
+ })