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

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 (481) 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/billing/InvoicesPagination.js +2 -2
  8. package/dist/components/dashboard/block-editor/block-picker.d.ts.map +1 -1
  9. package/dist/components/dashboard/block-editor/block-picker.js +2 -1
  10. package/dist/components/dashboard/block-editor/builder-editor-view.d.ts.map +1 -1
  11. package/dist/components/dashboard/block-editor/builder-editor-view.js +39 -23
  12. package/dist/components/dashboard/block-editor/pattern-reference-preview.d.ts.map +1 -1
  13. package/dist/components/dashboard/block-editor/pattern-reference-preview.js +2 -1
  14. package/dist/components/dashboard/layouts/AuthGuard.d.ts.map +1 -1
  15. package/dist/components/dashboard/layouts/AuthGuard.js +3 -4
  16. package/dist/components/dashboard/layouts/AuthenticatedDashboardLayout.d.ts.map +1 -1
  17. package/dist/components/dashboard/layouts/AuthenticatedDashboardLayout.js +9 -10
  18. package/dist/components/dashboard/layouts/DashboardAuthLayout.d.ts.map +1 -1
  19. package/dist/components/dashboard/layouts/DashboardAuthLayout.js +3 -4
  20. package/dist/components/dashboard/layouts/DashboardAuthSkeleton.d.ts +2 -2
  21. package/dist/components/dashboard/navigation/DynamicNavigation.js +2 -2
  22. package/dist/components/entities/wrappers/EntityDetailWrapper.d.ts.map +1 -1
  23. package/dist/components/entities/wrappers/EntityDetailWrapper.js +5 -2
  24. package/dist/components/entities/wrappers/EntityFormWrapper.d.ts.map +1 -1
  25. package/dist/components/entities/wrappers/EntityFormWrapper.js +5 -2
  26. package/dist/components/entities/wrappers/EntityListWrapper.d.ts.map +1 -1
  27. package/dist/components/entities/wrappers/EntityListWrapper.js +5 -2
  28. package/dist/components/settings/layouts/SettingsSidebar.d.ts.map +1 -1
  29. package/dist/components/settings/layouts/SettingsSidebar.js +0 -1
  30. package/dist/components/teams/CreateTeamButton.js +2 -2
  31. package/dist/components/teams/CreateTeamDialog.js +1 -1
  32. package/dist/components/teams/InviteMemberButton.js +3 -3
  33. package/dist/components/teams/InviteMemberDialog.js +1 -1
  34. package/dist/components/teams/TeamPendingInvitations.js +2 -2
  35. package/dist/components/ui/relation-display.d.ts.map +1 -1
  36. package/dist/components/ui/relation-display.js +2 -1
  37. package/dist/components/ui/user-display.d.ts.map +1 -1
  38. package/dist/components/ui/user-display.js +2 -1
  39. package/dist/emails/otp-verification.d.ts.map +1 -1
  40. package/dist/emails/otp-verification.js +5 -3
  41. package/dist/emails/reset-password.d.ts.map +1 -1
  42. package/dist/emails/reset-password.js +5 -3
  43. package/dist/emails/team-invitation.d.ts.map +1 -1
  44. package/dist/emails/team-invitation.js +3 -2
  45. package/dist/emails/verify-email.d.ts.map +1 -1
  46. package/dist/emails/verify-email.js +5 -3
  47. package/dist/hooks/useAuth.d.ts.map +1 -1
  48. package/dist/hooks/useAuth.js +21 -4
  49. package/dist/hooks/usePatternUsages.d.ts.map +1 -1
  50. package/dist/hooks/usePatternUsages.js +3 -2
  51. package/dist/lib/api/api-error.d.ts +2 -0
  52. package/dist/lib/api/api-error.d.ts.map +1 -1
  53. package/dist/lib/api/api-error.js +3 -1
  54. package/dist/lib/api/auth/dual-auth.d.ts +2 -1
  55. package/dist/lib/api/auth/dual-auth.d.ts.map +1 -1
  56. package/dist/lib/api/auth/dual-auth.js +22 -4
  57. package/dist/lib/api/auth/scope-policy.d.ts +6 -3
  58. package/dist/lib/api/auth/scope-policy.d.ts.map +1 -1
  59. package/dist/lib/api/auth.d.ts +2 -0
  60. package/dist/lib/api/auth.d.ts.map +1 -1
  61. package/dist/lib/api/auth.js +10 -10
  62. package/dist/lib/api/client-headers.d.ts +11 -0
  63. package/dist/lib/api/client-headers.d.ts.map +1 -0
  64. package/dist/lib/api/client-headers.js +12 -0
  65. package/dist/lib/api/client-ip.d.ts +59 -0
  66. package/dist/lib/api/client-ip.d.ts.map +1 -0
  67. package/dist/lib/api/client-ip.js +91 -0
  68. package/dist/lib/api/cors-response.d.ts +55 -0
  69. package/dist/lib/api/cors-response.d.ts.map +1 -0
  70. package/dist/lib/api/cors-response.js +90 -0
  71. package/dist/lib/api/entities.d.ts.map +1 -1
  72. package/dist/lib/api/entities.js +4 -3
  73. package/dist/lib/api/entity/audit-log.d.ts.map +1 -1
  74. package/dist/lib/api/entity/audit-log.js +2 -1
  75. package/dist/lib/api/entity/generic-handler.d.ts.map +1 -1
  76. package/dist/lib/api/entity/generic-handler.js +23 -8
  77. package/dist/lib/api/helpers.d.ts +8 -2
  78. package/dist/lib/api/helpers.d.ts.map +1 -1
  79. package/dist/lib/api/helpers.js +32 -103
  80. package/dist/lib/api/keys.d.ts +6 -0
  81. package/dist/lib/api/keys.d.ts.map +1 -1
  82. package/dist/lib/api/keys.js +7 -1
  83. package/dist/lib/api/permission-middleware.d.ts.map +1 -1
  84. package/dist/lib/api/permission-middleware.js +0 -2
  85. package/dist/lib/api/rate-limit.d.ts +12 -0
  86. package/dist/lib/api/rate-limit.d.ts.map +1 -1
  87. package/dist/lib/api/rate-limit.js +50 -43
  88. package/dist/lib/api/request-origin.d.ts +16 -7
  89. package/dist/lib/api/request-origin.d.ts.map +1 -1
  90. package/dist/lib/api/request-origin.js +43 -20
  91. package/dist/lib/auth/load-login-page.d.ts +10 -0
  92. package/dist/lib/auth/load-login-page.d.ts.map +1 -0
  93. package/dist/lib/auth/load-login-page.js +7 -0
  94. package/dist/lib/auth/runtime-readiness.d.ts.map +1 -1
  95. package/dist/lib/auth/runtime-readiness.js +4 -0
  96. package/dist/lib/auth/sample-accounts.d.ts +17 -0
  97. package/dist/lib/auth/sample-accounts.d.ts.map +1 -0
  98. package/dist/lib/auth/sample-accounts.js +22 -0
  99. package/dist/lib/auth/security-notifications/device-fingerprint.d.ts +1 -8
  100. package/dist/lib/auth/security-notifications/device-fingerprint.d.ts.map +1 -1
  101. package/dist/lib/auth/security-notifications/device-fingerprint.js +2 -12
  102. package/dist/lib/auth-email-callbacks.js +3 -3
  103. package/dist/lib/auth.d.ts.map +1 -1
  104. package/dist/lib/auth.js +13 -5
  105. package/dist/lib/cache/public-entity-cache.d.ts +21 -0
  106. package/dist/lib/cache/public-entity-cache.d.ts.map +1 -0
  107. package/dist/lib/cache/public-entity-cache.js +25 -0
  108. package/dist/lib/config/config-client.d.ts.map +1 -1
  109. package/dist/lib/config/config-client.js +1 -1
  110. package/dist/lib/config/config-sync.d.ts.map +1 -1
  111. package/dist/lib/config/config-sync.js +1 -1
  112. package/dist/lib/config/public-config-client.d.ts +1 -0
  113. package/dist/lib/config/public-config-client.d.ts.map +1 -1
  114. package/dist/lib/config/public-config-client.js +3 -1
  115. package/dist/lib/config/types.d.ts +2 -0
  116. package/dist/lib/config/types.d.ts.map +1 -1
  117. package/dist/lib/constants/reserved-slugs.d.ts +17 -8
  118. package/dist/lib/constants/reserved-slugs.d.ts.map +1 -1
  119. package/dist/lib/constants/reserved-slugs.js +16 -52
  120. package/dist/lib/db.d.ts.map +1 -1
  121. package/dist/lib/db.js +9 -3
  122. package/dist/lib/entities/entity-hooks.d.ts.map +1 -1
  123. package/dist/lib/entities/entity-hooks.js +4 -0
  124. package/dist/lib/entities/external-api-generator.d.ts.map +1 -1
  125. package/dist/lib/entities/external-api-generator.js +2 -4
  126. package/dist/lib/entities/public-slug.d.ts +46 -0
  127. package/dist/lib/entities/public-slug.d.ts.map +1 -0
  128. package/dist/lib/entities/public-slug.js +39 -0
  129. package/dist/lib/mcp/errors.d.ts +2 -0
  130. package/dist/lib/mcp/errors.d.ts.map +1 -1
  131. package/dist/lib/mcp/errors.js +3 -1
  132. package/dist/lib/mcp/index.d.ts +1 -1
  133. package/dist/lib/mcp/index.d.ts.map +1 -1
  134. package/dist/lib/mcp/index.js +2 -1
  135. package/dist/lib/services/generic-entity.service.d.ts.map +1 -1
  136. package/dist/lib/services/generic-entity.service.js +2 -0
  137. package/dist/lib/teams/actions.d.ts.map +1 -1
  138. package/dist/lib/teams/actions.js +0 -1
  139. package/dist/messages/de/billing.json +12 -1
  140. package/dist/messages/de/common.json +2 -0
  141. package/dist/messages/de/docs.json +1 -0
  142. package/dist/messages/de/features.json +16 -4
  143. package/dist/messages/de/home.json +5 -0
  144. package/dist/messages/de/index.d.ts +68 -25
  145. package/dist/messages/de/index.d.ts.map +1 -1
  146. package/dist/messages/de/index.js +2 -0
  147. package/dist/messages/de/index.ts +2 -0
  148. package/dist/messages/de/settings.json +1 -0
  149. package/dist/messages/de/teams.json +3 -18
  150. package/dist/messages/en/billing.json +1 -1
  151. package/dist/messages/en/common.json +2 -0
  152. package/dist/messages/en/docs.json +1 -0
  153. package/dist/messages/en/features.json +16 -4
  154. package/dist/messages/en/home.json +5 -0
  155. package/dist/messages/en/index.d.ts +58 -25
  156. package/dist/messages/en/index.d.ts.map +1 -1
  157. package/dist/messages/en/index.js +2 -0
  158. package/dist/messages/en/index.ts +2 -0
  159. package/dist/messages/en/settings.json +1 -0
  160. package/dist/messages/en/teams.json +3 -18
  161. package/dist/messages/es/billing.json +1 -1
  162. package/dist/messages/es/common.json +2 -0
  163. package/dist/messages/es/docs.json +1 -0
  164. package/dist/messages/es/features.json +16 -4
  165. package/dist/messages/es/home.json +5 -0
  166. package/dist/messages/es/index.d.ts +41 -8
  167. package/dist/messages/es/index.d.ts.map +1 -1
  168. package/dist/messages/es/index.js +2 -0
  169. package/dist/messages/es/index.ts +2 -0
  170. package/dist/messages/es/settings.json +1 -0
  171. package/dist/messages/es/teams.json +3 -0
  172. package/dist/messages/fr/billing.json +12 -1
  173. package/dist/messages/fr/common.json +2 -0
  174. package/dist/messages/fr/docs.json +1 -0
  175. package/dist/messages/fr/features.json +16 -4
  176. package/dist/messages/fr/home.json +5 -0
  177. package/dist/messages/fr/index.d.ts +68 -25
  178. package/dist/messages/fr/index.d.ts.map +1 -1
  179. package/dist/messages/fr/index.js +2 -0
  180. package/dist/messages/fr/index.ts +2 -0
  181. package/dist/messages/fr/settings.json +1 -0
  182. package/dist/messages/fr/teams.json +3 -18
  183. package/dist/messages/it/billing.json +12 -1
  184. package/dist/messages/it/common.json +2 -0
  185. package/dist/messages/it/docs.json +1 -0
  186. package/dist/messages/it/features.json +16 -4
  187. package/dist/messages/it/home.json +5 -0
  188. package/dist/messages/it/index.d.ts +68 -25
  189. package/dist/messages/it/index.d.ts.map +1 -1
  190. package/dist/messages/it/index.js +2 -0
  191. package/dist/messages/it/index.ts +2 -0
  192. package/dist/messages/it/settings.json +1 -0
  193. package/dist/messages/it/teams.json +3 -18
  194. package/dist/messages/pt/billing.json +12 -1
  195. package/dist/messages/pt/common.json +2 -0
  196. package/dist/messages/pt/docs.json +1 -0
  197. package/dist/messages/pt/features.json +16 -4
  198. package/dist/messages/pt/home.json +5 -0
  199. package/dist/messages/pt/index.d.ts +68 -25
  200. package/dist/messages/pt/index.d.ts.map +1 -1
  201. package/dist/messages/pt/index.js +2 -0
  202. package/dist/messages/pt/index.ts +2 -0
  203. package/dist/messages/pt/settings.json +1 -0
  204. package/dist/messages/pt/teams.json +3 -18
  205. package/dist/migrations/029_sample_accounts_outside_development.sql +55 -0
  206. package/dist/migrations/090_sample_data.sql +10 -0
  207. package/dist/routes/(auth)/signup/page.cc.d.ts +3 -8
  208. package/dist/routes/(auth)/signup/page.cc.d.ts.map +1 -1
  209. package/dist/routes/(auth)/signup/page.cc.js +7 -1
  210. package/dist/routes/(auth)/verify-email/page.d.ts.map +1 -1
  211. package/dist/routes/(auth)/verify-email/page.js +2 -1
  212. package/dist/routes/(public)/page.cc.d.ts +3 -0
  213. package/dist/routes/(public)/page.cc.d.ts.map +1 -0
  214. package/dist/routes/(public)/page.cc.js +3 -0
  215. package/dist/routes/_internal/area-access.cc.d.ts +21 -0
  216. package/dist/routes/_internal/area-access.cc.d.ts.map +1 -0
  217. package/dist/routes/_internal/area-access.cc.js +19 -0
  218. package/dist/routes/_internal/auth-layout.d.ts.map +1 -1
  219. package/dist/routes/_internal/auth-layout.js +3 -2
  220. package/dist/routes/_internal/dashboard-main-shared.d.ts +7 -0
  221. package/dist/routes/_internal/dashboard-main-shared.d.ts.map +1 -0
  222. package/dist/routes/_internal/dashboard-main-shared.js +49 -0
  223. package/dist/routes/_internal/default-auth-layout.d.ts.map +1 -1
  224. package/dist/routes/_internal/default-auth-layout.js +3 -2
  225. package/dist/routes/_internal/devtools-layout.cc.d.ts +3 -2
  226. package/dist/routes/_internal/devtools-layout.cc.d.ts.map +1 -1
  227. package/dist/routes/_internal/devtools-layout.cc.js +1 -1
  228. package/dist/routes/_internal/devtools-layout.d.ts +7 -2
  229. package/dist/routes/_internal/devtools-layout.d.ts.map +1 -1
  230. package/dist/routes/_internal/devtools-layout.js +4 -4
  231. package/dist/routes/_internal/entity-detail-route.cc.d.ts +8 -0
  232. package/dist/routes/_internal/entity-detail-route.cc.d.ts.map +1 -0
  233. package/dist/routes/_internal/entity-detail-route.cc.js +5 -0
  234. package/dist/routes/_internal/entity-edit-route.cc.d.ts +8 -0
  235. package/dist/routes/_internal/entity-edit-route.cc.d.ts.map +1 -0
  236. package/dist/routes/_internal/entity-edit-route.cc.js +5 -0
  237. package/dist/routes/_internal/entity-permission-layout.d.ts.map +1 -1
  238. package/dist/routes/_internal/entity-permission-layout.js +0 -7
  239. package/dist/routes/_internal/public-archive-route.d.ts +1 -1
  240. package/dist/routes/_internal/public-archive-route.d.ts.map +1 -1
  241. package/dist/routes/_internal/public-archive-route.js +8 -3
  242. package/dist/routes/_internal/public-item-route.cc.d.ts +5 -1
  243. package/dist/routes/_internal/public-item-route.cc.d.ts.map +1 -1
  244. package/dist/routes/_internal/public-item-route.cc.js +2 -1
  245. package/dist/routes/_internal/public-item-route.d.ts +7 -0
  246. package/dist/routes/_internal/public-item-route.d.ts.map +1 -1
  247. package/dist/routes/_internal/public-item-route.js +6 -1
  248. package/dist/routes/_internal/superadmin-layout.cc.d.ts +2 -1
  249. package/dist/routes/_internal/superadmin-layout.cc.d.ts.map +1 -1
  250. package/dist/routes/_internal/superadmin-layout.cc.js +1 -1
  251. package/dist/routes/_internal/superadmin-layout.d.ts +7 -2
  252. package/dist/routes/_internal/superadmin-layout.d.ts.map +1 -1
  253. package/dist/routes/_internal/superadmin-layout.js +6 -6
  254. package/dist/routes/_internal/suspended-route.d.ts +16 -0
  255. package/dist/routes/_internal/suspended-route.d.ts.map +1 -0
  256. package/dist/routes/_internal/suspended-route.js +7 -0
  257. package/dist/routes/api/auth/[...all]/route.d.ts.map +1 -1
  258. package/dist/routes/api/auth/[...all]/route.js +4 -12
  259. package/dist/routes/api/csp-report/route.d.ts.map +1 -1
  260. package/dist/routes/api/csp-report/route.js +2 -13
  261. package/dist/routes/api/devtools/config/entities/route.d.ts +2 -0
  262. package/dist/routes/api/devtools/config/entities/route.d.ts.map +1 -1
  263. package/dist/routes/api/devtools/config/entities/route.js +2 -0
  264. package/dist/routes/api/devtools/config/theme/route.d.ts +2 -0
  265. package/dist/routes/api/devtools/config/theme/route.d.ts.map +1 -1
  266. package/dist/routes/api/devtools/config/theme/route.js +2 -0
  267. package/dist/routes/api/devtools/tests/[...path]/route.d.ts +2 -0
  268. package/dist/routes/api/devtools/tests/[...path]/route.d.ts.map +1 -1
  269. package/dist/routes/api/devtools/tests/[...path]/route.js +2 -0
  270. package/dist/routes/api/devtools/tests/route.d.ts +2 -0
  271. package/dist/routes/api/devtools/tests/route.d.ts.map +1 -1
  272. package/dist/routes/api/devtools/tests/route.js +2 -0
  273. package/dist/routes/api/health/route.d.ts +2 -0
  274. package/dist/routes/api/health/route.d.ts.map +1 -1
  275. package/dist/routes/api/health/route.js +2 -0
  276. package/dist/routes/api/internal/user-metadata/route.d.ts +2 -0
  277. package/dist/routes/api/internal/user-metadata/route.d.ts.map +1 -1
  278. package/dist/routes/api/internal/user-metadata/route.js +2 -0
  279. package/dist/routes/api/mcp/route.d.ts +2 -0
  280. package/dist/routes/api/mcp/route.d.ts.map +1 -1
  281. package/dist/routes/api/mcp/route.js +9 -1
  282. package/dist/routes/api/superadmin/subscriptions/route.d.ts +2 -0
  283. package/dist/routes/api/superadmin/subscriptions/route.d.ts.map +1 -1
  284. package/dist/routes/api/superadmin/subscriptions/route.js +2 -0
  285. package/dist/routes/api/superadmin/teams/[teamId]/route.d.ts +2 -0
  286. package/dist/routes/api/superadmin/teams/[teamId]/route.d.ts.map +1 -1
  287. package/dist/routes/api/superadmin/teams/[teamId]/route.js +2 -0
  288. package/dist/routes/api/superadmin/teams/route.d.ts +2 -0
  289. package/dist/routes/api/superadmin/teams/route.d.ts.map +1 -1
  290. package/dist/routes/api/superadmin/teams/route.js +2 -0
  291. package/dist/routes/api/superadmin/users/[userId]/route.d.ts +2 -0
  292. package/dist/routes/api/superadmin/users/[userId]/route.d.ts.map +1 -1
  293. package/dist/routes/api/superadmin/users/[userId]/route.js +2 -0
  294. package/dist/routes/api/superadmin/users/route.d.ts +2 -0
  295. package/dist/routes/api/superadmin/users/route.d.ts.map +1 -1
  296. package/dist/routes/api/superadmin/users/route.js +2 -0
  297. package/dist/routes/api/user/delete-account/route.d.ts +2 -0
  298. package/dist/routes/api/user/delete-account/route.d.ts.map +1 -1
  299. package/dist/routes/api/user/delete-account/route.js +2 -0
  300. package/dist/routes/api/user/plan-flags/route.d.ts +2 -0
  301. package/dist/routes/api/user/plan-flags/route.d.ts.map +1 -1
  302. package/dist/routes/api/user/plan-flags/route.js +2 -0
  303. package/dist/routes/api/user/profile/route.d.ts +2 -0
  304. package/dist/routes/api/user/profile/route.d.ts.map +1 -1
  305. package/dist/routes/api/user/profile/route.js +4 -3
  306. package/dist/routes/api/v1/[entity]/[id]/child/[childType]/[childId]/route.d.ts +2 -0
  307. package/dist/routes/api/v1/[entity]/[id]/child/[childType]/[childId]/route.d.ts.map +1 -1
  308. package/dist/routes/api/v1/[entity]/[id]/child/[childType]/[childId]/route.js +2 -0
  309. package/dist/routes/api/v1/[entity]/[id]/child/[childType]/route.d.ts +2 -0
  310. package/dist/routes/api/v1/[entity]/[id]/child/[childType]/route.d.ts.map +1 -1
  311. package/dist/routes/api/v1/[entity]/[id]/child/[childType]/route.js +2 -0
  312. package/dist/routes/api/v1/billing/cancel/route.d.ts +2 -0
  313. package/dist/routes/api/v1/billing/cancel/route.d.ts.map +1 -1
  314. package/dist/routes/api/v1/billing/cancel/route.js +2 -0
  315. package/dist/routes/api/v1/billing/change-plan/route.d.ts +2 -0
  316. package/dist/routes/api/v1/billing/change-plan/route.d.ts.map +1 -1
  317. package/dist/routes/api/v1/billing/change-plan/route.js +2 -0
  318. package/dist/routes/api/v1/billing/check-action/route.d.ts +2 -0
  319. package/dist/routes/api/v1/billing/check-action/route.d.ts.map +1 -1
  320. package/dist/routes/api/v1/billing/check-action/route.js +2 -0
  321. package/dist/routes/api/v1/billing/checkout/route.d.ts +2 -0
  322. package/dist/routes/api/v1/billing/checkout/route.d.ts.map +1 -1
  323. package/dist/routes/api/v1/billing/checkout/route.js +2 -0
  324. package/dist/routes/api/v1/billing/plans/route.d.ts +2 -0
  325. package/dist/routes/api/v1/billing/plans/route.d.ts.map +1 -1
  326. package/dist/routes/api/v1/billing/plans/route.js +2 -0
  327. package/dist/routes/api/v1/billing/portal/route.d.ts +2 -0
  328. package/dist/routes/api/v1/billing/portal/route.d.ts.map +1 -1
  329. package/dist/routes/api/v1/billing/portal/route.js +2 -0
  330. package/dist/routes/api/v1/blocks/[slug]/route.d.ts +2 -0
  331. package/dist/routes/api/v1/blocks/[slug]/route.d.ts.map +1 -1
  332. package/dist/routes/api/v1/blocks/[slug]/route.js +2 -0
  333. package/dist/routes/api/v1/blocks/route.d.ts +2 -0
  334. package/dist/routes/api/v1/blocks/route.d.ts.map +1 -1
  335. package/dist/routes/api/v1/blocks/route.js +2 -0
  336. package/dist/routes/api/v1/blocks/validate/route.d.ts +2 -0
  337. package/dist/routes/api/v1/blocks/validate/route.d.ts.map +1 -1
  338. package/dist/routes/api/v1/blocks/validate/route.js +2 -0
  339. package/dist/routes/api/v1/devtools/blocks/route.d.ts +2 -1
  340. package/dist/routes/api/v1/devtools/blocks/route.d.ts.map +1 -1
  341. package/dist/routes/api/v1/devtools/blocks/route.js +2 -9
  342. package/dist/routes/api/v1/devtools/docs/route.d.ts +2 -0
  343. package/dist/routes/api/v1/devtools/docs/route.d.ts.map +1 -1
  344. package/dist/routes/api/v1/devtools/docs/route.js +2 -0
  345. package/dist/routes/api/v1/devtools/features/route.d.ts +2 -1
  346. package/dist/routes/api/v1/devtools/features/route.d.ts.map +1 -1
  347. package/dist/routes/api/v1/devtools/features/route.js +2 -9
  348. package/dist/routes/api/v1/devtools/flows/route.d.ts +2 -1
  349. package/dist/routes/api/v1/devtools/flows/route.d.ts.map +1 -1
  350. package/dist/routes/api/v1/devtools/flows/route.js +2 -9
  351. package/dist/routes/api/v1/devtools/scheduled-actions/route.d.ts +2 -1
  352. package/dist/routes/api/v1/devtools/scheduled-actions/route.d.ts.map +1 -1
  353. package/dist/routes/api/v1/devtools/scheduled-actions/route.js +2 -9
  354. package/dist/routes/api/v1/devtools/scheduled-actions/run/route.d.ts +2 -1
  355. package/dist/routes/api/v1/devtools/scheduled-actions/run/route.d.ts.map +1 -1
  356. package/dist/routes/api/v1/devtools/scheduled-actions/run/route.js +2 -9
  357. package/dist/routes/api/v1/devtools/testing/route.d.ts +2 -1
  358. package/dist/routes/api/v1/devtools/testing/route.d.ts.map +1 -1
  359. package/dist/routes/api/v1/devtools/testing/route.js +2 -9
  360. package/dist/routes/api/v1/media/[id]/route.d.ts +2 -0
  361. package/dist/routes/api/v1/media/[id]/route.d.ts.map +1 -1
  362. package/dist/routes/api/v1/media/[id]/route.js +2 -0
  363. package/dist/routes/api/v1/media/[id]/tags/route.d.ts +2 -0
  364. package/dist/routes/api/v1/media/[id]/tags/route.d.ts.map +1 -1
  365. package/dist/routes/api/v1/media/[id]/tags/route.js +2 -0
  366. package/dist/routes/api/v1/media/check-duplicates/route.d.ts +2 -0
  367. package/dist/routes/api/v1/media/check-duplicates/route.d.ts.map +1 -1
  368. package/dist/routes/api/v1/media/check-duplicates/route.js +2 -0
  369. package/dist/routes/api/v1/media/route.d.ts +2 -0
  370. package/dist/routes/api/v1/media/route.d.ts.map +1 -1
  371. package/dist/routes/api/v1/media/route.js +2 -0
  372. package/dist/routes/api/v1/media/upload/route.d.ts +4 -1
  373. package/dist/routes/api/v1/media/upload/route.d.ts.map +1 -1
  374. package/dist/routes/api/v1/media/upload/route.js +12 -2
  375. package/dist/routes/api/v1/media-tags/route.d.ts +2 -0
  376. package/dist/routes/api/v1/media-tags/route.d.ts.map +1 -1
  377. package/dist/routes/api/v1/media-tags/route.js +2 -0
  378. package/dist/routes/api/v1/post-categories/[id]/route.d.ts +2 -0
  379. package/dist/routes/api/v1/post-categories/[id]/route.d.ts.map +1 -1
  380. package/dist/routes/api/v1/post-categories/[id]/route.js +2 -0
  381. package/dist/routes/api/v1/post-categories/route.d.ts +2 -0
  382. package/dist/routes/api/v1/post-categories/route.d.ts.map +1 -1
  383. package/dist/routes/api/v1/post-categories/route.js +2 -0
  384. package/dist/routes/api/v1/teams/[teamId]/members/route.d.ts.map +1 -1
  385. package/dist/routes/api/v1/teams/[teamId]/members/route.js +14 -12
  386. package/dist/routes/api/v1/teams/[teamId]/subscription/route.d.ts +2 -0
  387. package/dist/routes/api/v1/teams/[teamId]/subscription/route.d.ts.map +1 -1
  388. package/dist/routes/api/v1/teams/[teamId]/subscription/route.js +2 -0
  389. package/dist/routes/api/v1/teams/[teamId]/usage/[limitSlug]/route.d.ts +2 -0
  390. package/dist/routes/api/v1/teams/[teamId]/usage/[limitSlug]/route.d.ts.map +1 -1
  391. package/dist/routes/api/v1/teams/[teamId]/usage/[limitSlug]/route.js +2 -0
  392. package/dist/routes/api/v1/teams/switch/route.d.ts.map +1 -1
  393. package/dist/routes/api/v1/teams/switch/route.js +4 -1
  394. package/dist/routes/api/v1/users/[id]/route.d.ts.map +1 -1
  395. package/dist/routes/api/v1/users/[id]/route.js +3 -2
  396. package/dist/routes/api/v1/users/docs.md +34 -19
  397. package/dist/routes/api/v1/users/me/route.d.ts +6 -0
  398. package/dist/routes/api/v1/users/me/route.d.ts.map +1 -0
  399. package/dist/routes/api/v1/users/me/route.js +5 -0
  400. package/dist/routes/api/v1/users/presets.ts +2 -2
  401. package/dist/routes/dashboard/(main)/layout.cc.d.ts +4 -0
  402. package/dist/routes/dashboard/(main)/layout.cc.d.ts.map +1 -0
  403. package/dist/routes/dashboard/(main)/layout.cc.js +10 -0
  404. package/dist/routes/dashboard/(main)/layout.d.ts.map +1 -1
  405. package/dist/routes/dashboard/(main)/layout.js +3 -44
  406. package/dist/routes/dashboard/permission-denied/page.d.ts +1 -1
  407. package/dist/routes/dashboard/permission-denied/page.d.ts.map +1 -1
  408. package/dist/routes/dashboard/permission-denied/page.js +5 -1
  409. package/dist/routes/dashboard/settings/password/page.d.ts.map +1 -1
  410. package/dist/routes/dashboard/settings/password/page.js +10 -12
  411. package/dist/routes/devtools/config/page.js +1 -1
  412. package/dist/routes/devtools/layout.cc.d.ts.map +1 -1
  413. package/dist/routes/devtools/layout.cc.js +5 -1
  414. package/dist/routes/devtools/page.js +1 -1
  415. package/dist/routes/devtools/scheduled-actions/page.js +1 -1
  416. package/dist/routes/devtools/tests/[[...path]]/page.js +1 -1
  417. package/dist/routes/manifest.json +10 -0
  418. package/dist/routes/robots.d.ts +10 -0
  419. package/dist/routes/robots.d.ts.map +1 -0
  420. package/dist/routes/robots.js +10 -0
  421. package/dist/routes/superadmin/layout.cc.d.ts.map +1 -1
  422. package/dist/routes/superadmin/layout.cc.js +5 -1
  423. package/dist/routes/variants.json +12 -2
  424. package/dist/templates/ci-workflows/github/cypress-regression.yml +1 -1
  425. package/dist/templates/ci-workflows/github/cypress-smoke.yml +2 -1
  426. package/dist/templates/next.config.mjs +2 -29
  427. package/dist/templates/projects/blog/api/authors/[username]/route.ts +3 -0
  428. package/dist/templates/projects/blog/api/authors/route.ts +3 -0
  429. package/dist/templates/projects/blog/api/posts/public/route.ts +3 -0
  430. package/dist/templates/projects/starter/README.md +2 -0
  431. package/dist/templates/projects/starter/docs/public/01-overview/01-introduction.md +2 -0
  432. package/dist/templates/projects/starter/templates/dashboard/analytics/layout.tsx +14 -0
  433. package/dist/templates/proxy.ts +27 -9
  434. package/dist/templates/public/favicon.ico +0 -0
  435. package/dist/templates/public/uploads/README.md +5 -94
  436. package/migrations/029_sample_accounts_outside_development.sql +55 -0
  437. package/migrations/090_sample_data.sql +10 -0
  438. package/package.json +2 -2
  439. package/scripts/build/registry/__tests__/api-docs-colocated.test.mjs +3 -0
  440. package/scripts/build/registry/__tests__/billing-registry.test.mjs +50 -1
  441. package/scripts/build/registry/__tests__/block-registry.test.mjs +3 -0
  442. package/scripts/build/registry/__tests__/icons-discovery.test.mjs +3 -0
  443. package/scripts/build/registry/__tests__/quiet-console.mjs +17 -0
  444. package/scripts/build/registry/__tests__/safe-fs.test.mjs +3 -0
  445. package/scripts/build/registry/__tests__/team-roles.test.mjs +3 -0
  446. package/scripts/build/registry/discovery/permissions.test.mjs +3 -0
  447. package/scripts/build/registry/discovery/plugin-capabilities.mjs +2 -2
  448. package/scripts/build/registry/generators/billing-registry.mjs +7 -3
  449. package/scripts/build/registry/host/README.md +25 -1
  450. package/scripts/build/registry/host/__tests__/host-area-access.test.mjs +32 -0
  451. package/scripts/build/registry/host/__tests__/host-entities.test.mjs +32 -1
  452. package/scripts/build/registry/host/__tests__/host-plan.test.mjs +12 -2
  453. package/scripts/build/registry/host/__tests__/host-proxy-areas.test.mjs +72 -1
  454. package/scripts/build/registry/host/entity-routes.mjs +9 -3
  455. package/scripts/build/registry/host/proxy-areas.mjs +65 -2
  456. package/scripts/build/registry/host/static-imports.mjs +4 -0
  457. package/scripts/build/routes-manifest.mjs +13 -1
  458. package/scripts/db/run-migrations.mjs +30 -10
  459. package/scripts/db/sample-data.mjs +44 -0
  460. package/scripts/db/ssl-config.mjs +12 -1
  461. package/scripts/db/verify-theme-migrations.mjs +6 -0
  462. package/templates/ci-workflows/github/cypress-regression.yml +1 -1
  463. package/templates/ci-workflows/github/cypress-smoke.yml +2 -1
  464. package/templates/next.config.mjs +2 -29
  465. package/templates/projects/blog/api/authors/[username]/route.ts +3 -0
  466. package/templates/projects/blog/api/authors/route.ts +3 -0
  467. package/templates/projects/blog/api/posts/public/route.ts +3 -0
  468. package/templates/projects/starter/README.md +2 -0
  469. package/templates/projects/starter/docs/public/01-overview/01-introduction.md +2 -0
  470. package/templates/projects/starter/templates/dashboard/analytics/layout.tsx +14 -0
  471. package/templates/proxy.ts +27 -9
  472. package/templates/public/favicon.ico +0 -0
  473. package/templates/public/uploads/README.md +5 -94
  474. package/tests/jest/__mocks__/@nextsparkjs/registries/api-presets-registry.ts +2 -0
  475. package/tests/jest/__mocks__/@nextsparkjs/registries/mcp-registry.ts +2 -0
  476. package/dist/templates/projects/starter/public/brand/.gitkeep +0 -0
  477. package/dist/templates/public/uploads/.gitignore +0 -6
  478. package/dist/templates/public/uploads/temp/.gitkeep +0 -1
  479. package/templates/projects/starter/public/brand/.gitkeep +0 -0
  480. package/templates/public/uploads/.gitignore +0 -6
  481. package/templates/public/uploads/temp/.gitkeep +0 -1
@@ -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
+ })
@@ -15,7 +15,7 @@ import { dirname, join } from 'node:path'
15
15
  import { ENTITY_DIAGNOSTICS, ENTITY_MODULES, entityConfigFile, planEntityRoutes, readAllEntityFacts, readEntityFacts, routableEntities } from '../entity-routes.mjs'
16
16
  import { PLAN_DIAGNOSTICS, PLAN_NOTICES, planHost, urlConflicts } from '../plan.mjs'
17
17
  import { renderHost } from '../render.mjs'
18
- import { validateGeneratedModule } from '../static-imports.mjs'
18
+ import { CORE_COMPOSITION_WRAPPERS, validateGeneratedModule } from '../static-imports.mjs'
19
19
  import { loadTypeScriptFor } from '../../shared/typescript-compiler.mjs'
20
20
 
21
21
  const CORE_ROOT = join(import.meta.dirname, '../../../../..')
@@ -29,6 +29,8 @@ const CORE_MODULES = {
29
29
  [ENTITY_MODULES.detail]: ['core/entity-detail-route.tsx', factories('createEntityDetailRoute')],
30
30
  [ENTITY_MODULES.create]: ['core/entity-create-route.tsx', factories('createEntityCreateRoute')],
31
31
  [ENTITY_MODULES.edit]: ['core/entity-edit-route.tsx', factories('createEntityEditRoute')],
32
+ [ENTITY_MODULES.detailCc]: ['core/entity-detail-route.cc.tsx', factories('createEntityDetailRoute')],
33
+ [ENTITY_MODULES.editCc]: ['core/entity-edit-route.cc.tsx', factories('createEntityEditRoute')],
32
34
  [ENTITY_MODULES.publicItem]: ['core/public-item-route.tsx', factories('createPublicItemRoute', 'createPublicItemMetadata')],
33
35
  [ENTITY_MODULES.publicItemCc]: ['core/public-item-route.cc.tsx', factories('createPublicItemRoute', 'createPublicItemMetadata')],
34
36
  [ENTITY_MODULES.publicArchive]: ['core/public-archive-route.tsx', factories('createPublicArchiveRoute', 'createPublicArchiveMetadata')],
@@ -690,6 +692,35 @@ test('a host that builds in Cache Components mode only imports the cached item s
690
692
  }
691
693
  })
692
694
 
695
+ test('a Cache Components host writes the detail and edit pages of an entity from the modules that put them behind Suspense (they await `params`)', async () => {
696
+ const w = world()
697
+ try {
698
+ const args = [w, [entity('tasks', 'taskEntityConfig')], { tasks: await factsOf("slug: 'tasks', enabled: true, ui: { dashboard: { showInMenu: true } }") }]
699
+ const moduleOf = async (target, factory, options) => at(await render((await plan(...args, options)).routes), target).match(new RegExp(`import \\{ ${factory} \\} from "([^"]+)"`))[1]
700
+ assert.equal(ENTITY_MODULES.detailCc, `${ENTITY_MODULES.detail}.cc`)
701
+ assert.equal(ENTITY_MODULES.editCc, `${ENTITY_MODULES.edit}.cc`)
702
+ const detail = options => moduleOf('dashboard/(main)/tasks/[id]/page.tsx', 'createEntityDetailRoute', options)
703
+ const edit = options => moduleOf('dashboard/(main)/tasks/[id]/edit/page.tsx', 'createEntityEditRoute', options)
704
+ assert.equal(await detail({ cacheComponents: true }), ENTITY_MODULES.detailCc)
705
+ assert.equal(await edit({ cacheComponents: true }), ENTITY_MODULES.editCc)
706
+ // An ISR host keeps the routes that redirect and answer notFound() before anything is sent; so does one that builds both, or does not know
707
+ for (const options of [{ cacheComponents: false }, {}, { cacheComponents: true, modes: ['isr', 'cc'] }]) {
708
+ assert.equal(await detail(options), ENTITY_MODULES.detail)
709
+ assert.equal(await edit(options), ENTITY_MODULES.edit)
710
+ }
711
+ } finally {
712
+ w.cleanup()
713
+ }
714
+ })
715
+
716
+ test('every factory module the entity routes import is a known composition module, with the factories it exports (a real host fails facade validation otherwise)', () => {
717
+ const FACTORY_MODULES = ['layout', 'list', 'detail', 'create', 'edit', 'detailCc', 'editCc', 'publicItem', 'publicItemCc', 'publicArchive']
718
+ for (const name of FACTORY_MODULES) {
719
+ const wrappers = CORE_COMPOSITION_WRAPPERS[ENTITY_MODULES[name]]
720
+ assert.ok(Array.isArray(wrappers) && wrappers.length > 0, `${name}: ${ENTITY_MODULES[name]} is missing from CORE_COMPOSITION_WRAPPERS`)
721
+ }
722
+ })
723
+
693
724
  // ---------------------------------------------------------------------------
694
725
  // "The template replaces": project templates at an entity's dashboard URL in another route group
695
726
  // ---------------------------------------------------------------------------
@@ -53,6 +53,16 @@ test('core < plugin < project: a higher layer replaces a lower one at the same r
53
53
  }
54
54
  })
55
55
 
56
+ test('a project robots.ts replaces the robots route core ships (#214)', () => {
57
+ const { root, cleanup } = project({ 'templates/robots.ts': 'export default function robots() { return { rules: { userAgent: "*", disallow: "/" } } }\n' })
58
+ try {
59
+ const routes = resolveHostPlan({ coreRoutes: [core('robots.ts', 'robots')], plugins: [], project: { root } })
60
+ assert.deepEqual(summary(routes), ['robots.ts <- project:@/templates/robots (overrides core route @nextsparkjs/core/routes/robots)'])
61
+ } finally {
62
+ cleanup()
63
+ }
64
+ })
65
+
56
66
  test('the extension does not decide the slot: templates/about/page.ts replaces core about/page.tsx', () => {
57
67
  const { root, cleanup } = project({ 'templates/about/page.ts': PAGE })
58
68
  try {
@@ -330,8 +340,8 @@ test('core\'s routes carry their protection level, so a composed layout can be t
330
340
  assert.equal(withCc.routes.find(route => route.target === 'layout.tsx').compose.specifier, '@nextsparkjs/core/routes/_internal/root-layout.ppr')
331
341
  assert.equal(withCc.routes.find(route => route.target === '(auth)/login/page.tsx').specifier, '@nextsparkjs/core/routes/(auth)/login/page.cc')
332
342
  assert.deepEqual(withCc.variantsApplied.sort(), [
333
- '(auth)/layout.tsx', '(auth)/login/page.tsx', '(auth)/signup/page.tsx', '(public)/docs/[section]/[page]/page.tsx', '(public)/layout.tsx',
334
- 'dashboard/layout.tsx', 'devtools/layout.tsx', 'layout.tsx', 'superadmin/docs/[section]/[page]/page.tsx', 'superadmin/layout.tsx',
343
+ '(auth)/layout.tsx', '(auth)/login/page.tsx', '(auth)/signup/page.tsx', '(public)/docs/[section]/[page]/page.tsx', '(public)/layout.tsx', '(public)/page.tsx',
344
+ 'dashboard/(main)/layout.tsx', 'dashboard/layout.tsx', 'devtools/layout.tsx', 'layout.tsx', 'superadmin/docs/[section]/[page]/page.tsx', 'superadmin/layout.tsx',
335
345
  ])
336
346
  // The layouts that load messages keep their wrapper (from the Cache Components module) and their protection level
337
347
  const ccLevel = target => withCc.routes.find(route => route.target === target)
@@ -12,7 +12,7 @@ import { mkdtempSync, readFileSync, rmSync, symlinkSync } from 'node:fs'
12
12
  import { tmpdir } from 'node:os'
13
13
  import { join } from 'node:path'
14
14
 
15
- import { PROXY_AREA_WARNING, missingProxyAreas, proxyAreaNotice, proxyAreaNotices } from '../proxy-areas.mjs'
15
+ import { PROXY_AREA_WARNING, PROXY_SESSION_WARNING, missingProxyAreas, withoutComments, proxyAreaNotice, proxyAreaNotices, proxySessionNotice } from '../proxy-areas.mjs'
16
16
  import { predictHost } from '../prepare.mjs'
17
17
  import { PAGE, tempHost, write } from './host-helpers.mjs'
18
18
 
@@ -30,6 +30,20 @@ test('an area counts only when the proxy names its path; a look-alike prefix or
30
30
  assert.deepEqual(missingProxyAreas("const docs = '/superadmin-guide'; const tips = \"/devtools-tips\"").map(area => area.path), ['/superadmin', '/devtools'])
31
31
  })
32
32
 
33
+ test("a proxy that re-exports core's template has the check; a re-export of anything else does not", () => {
34
+ assert.deepEqual(missingProxyAreas("export { proxy } from '../../../packages/core/templates/proxy'\nexport const config = { matcher: [] }"), [])
35
+ assert.deepEqual(missingProxyAreas("export { proxy } from '@nextsparkjs/core/templates/proxy'"), [])
36
+ assert.deepEqual(missingProxyAreas("export { proxy } from './my-proxy'").map(area => area.path), ['/superadmin', '/devtools'])
37
+ assert.deepEqual(missingProxyAreas("export { other } from '@nextsparkjs/core/templates/proxy'").map(area => area.path), ['/superadmin', '/devtools'])
38
+ const both = ['/superadmin', '/devtools']
39
+ assert.deepEqual(missingProxyAreas("export { proxy as coreProxy } from '@nextsparkjs/core/templates/proxy'\nexport function proxy() {}").map(a => a.path), both)
40
+ assert.deepEqual(missingProxyAreas("export { other as proxy } from '@nextsparkjs/core/templates/proxy'").map(a => a.path), both)
41
+ assert.deepEqual(missingProxyAreas("export { proxy } from './vendor/core/templates/proxy'").map(a => a.path), both)
42
+ assert.deepEqual(missingProxyAreas("// export { proxy } from '@nextsparkjs/core/templates/proxy'\nexport function proxy() {}").map(a => a.path), both)
43
+ assert.deepEqual(missingProxyAreas("/*\nexport { proxy } from '@nextsparkjs/core/templates/proxy'\n*/\nexport function proxy() {}").map(a => a.path), both)
44
+ assert.deepEqual(missingProxyAreas("export { config, proxy } from '@nextsparkjs/core/templates/proxy.ts'"), [])
45
+ })
46
+
33
47
  test('the warning names the file, each missing area, its roles and where the check comes from', () => {
34
48
  const notice = proxyAreaNotice('src/proxy.ts', "const PROTECTED = ['/superadmin']")
35
49
  assert.equal(notice.code, PROXY_AREA_WARNING)
@@ -53,6 +67,63 @@ test('the proxy Next loads is the one checked; a project without one is not warn
53
67
  }
54
68
  })
55
69
 
70
+ // S80 (D1): the session check that fetched /api/auth/get-session from the request's origin, as every proxy.ts copied
71
+ // before 0.1.0-beta.196 does. Upgrading core never touches that copy, so prepare says what to change.
72
+ const HTTP_SESSION_CHECK = `function getSession(request: NextRequest) {
73
+ return betterFetch<Session>('/api/auth/get-session', {
74
+ baseURL: \`\${request.nextUrl.origin}\${request.nextUrl.basePath}\`,
75
+ headers: { cookie: request.headers.get('cookie') || '' },
76
+ })
77
+ }
78
+ const PROTECTED = ['/superadmin', '/devtools']
79
+ `
80
+
81
+ test('a proxy that asks its own /api/auth/get-session over HTTP is warned about, with the in-process call to use', () => {
82
+ const notice = proxySessionNotice('src/proxy.ts', HTTP_SESSION_CHECK)
83
+ assert.equal(notice.code, PROXY_SESSION_WARNING)
84
+ assert.match(notice.message, /^src\/proxy\.ts checks the session by fetching \/api\/auth\/get-session/)
85
+ assert.match(notice.message, /X-Forwarded-Proto: https/)
86
+ assert.match(notice.message, /auth\.api\.getSession\(\{ headers: new Headers\(\{ cookie: request\.headers\.get\('cookie'\) \|\| '' \}\), query: \{ disableRefresh: true \} \}\)/)
87
+ assert.equal(proxySessionNotice('src/proxy.ts', readFileSync(join(CORE_ROOT, 'templates/proxy.ts'), 'utf8')), null)
88
+ assert.equal(proxySessionNotice('src/proxy.ts', "// was: betterFetch('/api/auth/get-session')\nexport function proxy() {}"), null)
89
+ assert.equal(proxySessionNotice('src/proxy.ts', "export { proxy } from '@nextsparkjs/core/templates/proxy'"), null)
90
+ })
91
+
92
+ // The S80 review's measured edges: an end-of-line comment, and a '/*' / '*/' pair inside two string literals.
93
+ const COMMENT_EDGES = [
94
+ ['an end-of-line comment after code', "const x = 1 // was '/api/auth/get-session'\nexport function proxy() {}", false],
95
+ ['an end-of-line comment after code, no space', "const x = 1// was '/api/auth/get-session'", false],
96
+ ["a '/*' and a '*/' in two strings around the call", "const a = '/api/*'\nbetterFetch('/api/auth/get-session')\nconst b = 'x*/'", true],
97
+ ["a '//' inside a string before the call", "const u = 'https://example.com'; betterFetch('/api/auth/get-session')", true],
98
+ ['a block comment', "/* betterFetch('/api/auth/get-session') */ export function proxy() {}", false],
99
+ ['a quote escaped inside a string', "const q = 'it\\'s'; betterFetch('/api/auth/get-session')", true],
100
+ ]
101
+
102
+ test('comments are stripped wherever they start, and never inside a string literal', () => {
103
+ for (const [label, source, warns] of COMMENT_EDGES) {
104
+ assert.equal(proxySessionNotice('src/proxy.ts', source) !== null, warns, label)
105
+ }
106
+ assert.equal(withoutComments("a // b\nc"), 'a \nc')
107
+ assert.equal(withoutComments("'//' /* x */ \"/*\""), "'//' \"/*\"")
108
+ // The repository template, old and new, reads the same with comments gone: every regex literal in it survives.
109
+ const template = readFileSync(join(CORE_ROOT, 'templates/proxy.ts'), 'utf8')
110
+ assert.match(withoutComments(template), /\.replace\(\/\\\\\/g, '\/'\)\.replace\(\/\\\/\\\/\+\/g, '\/'\)/)
111
+ })
112
+
113
+ test("the proxy Next loads gets the session warning next to the areas one; core's template gets neither", () => {
114
+ const root = mkdtempSync(join(tmpdir(), 'nextspark-proxy-session-'))
115
+ try {
116
+ write(root, 'src/proxy.ts', HTTP_SESSION_CHECK)
117
+ assert.deepEqual(proxyAreaNotices(root).map(notice => notice.code), [PROXY_SESSION_WARNING])
118
+ write(root, 'src/proxy.ts', `${HTTP_SESSION_CHECK.replace("['/superadmin', '/devtools']", '[]')}`)
119
+ assert.deepEqual(proxyAreaNotices(root).map(notice => notice.code), [PROXY_AREA_WARNING, PROXY_SESSION_WARNING])
120
+ write(root, 'src/proxy.ts', readFileSync(join(CORE_ROOT, 'templates/proxy.ts'), 'utf8'))
121
+ assert.deepEqual(proxyAreaNotices(root), [])
122
+ } finally {
123
+ rmSync(root, { recursive: true, force: true })
124
+ }
125
+ })
126
+
56
127
  test("prepare's prediction carries the warning among its notices", async () => {
57
128
  const host = tempHost({ 'templates/about/page.tsx': PAGE('About') })
58
129
  const project = mkdtempSync(join(tmpdir(), 'nextspark-proxy-areas-project-'))
@@ -54,6 +54,9 @@ export const ENTITY_MODULES = Object.freeze({
54
54
  detail: `${CORE_ROUTES_SPECIFIER}_internal/entity-detail-route`,
55
55
  create: `${CORE_ROUTES_SPECIFIER}_internal/entity-create-route`,
56
56
  edit: `${CORE_ROUTES_SPECIFIER}_internal/entity-edit-route`,
57
+ // The detail and edit pages of a host with `cacheComponents` on: the same routes behind a Suspense boundary (they await `params`).
58
+ detailCc: `${CORE_ROUTES_SPECIFIER}_internal/entity-detail-route.cc`,
59
+ editCc: `${CORE_ROUTES_SPECIFIER}_internal/entity-edit-route.cc`,
57
60
  publicItem: `${CORE_ROUTES_SPECIFIER}_internal/public-item-route`,
58
61
  // The item pages of a host with `cacheComponents` on: same factories, `'use cache'` reads instead of `revalidate`.
59
62
  publicItemCc: `${CORE_ROUTES_SPECIFIER}_internal/public-item-route.cc`,
@@ -207,7 +210,10 @@ export function planEntityRoutes({ entities, facts, coreRoutes, resolveFile, cac
207
210
  const revalidates = cacheComponents !== true && !modes.includes('cc')
208
211
  // A host that builds in Cache Components mode only gets the cached item source; one that builds both (the
209
212
  // conformance fixture) or does not know keeps the module that works in either.
210
- const publicItemModule = cacheComponents === true && modes.length === 0 && modules.publicItemCc ? modules.publicItemCc : modules.publicItem
213
+ const ccOnly = cacheComponents === true && modes.length === 0
214
+ const publicItemModule = ccOnly && modules.publicItemCc ? modules.publicItemCc : modules.publicItem
215
+ const detailModule = ccOnly && modules.detailCc ? modules.detailCc : modules.detail
216
+ const editModule = ccOnly && modules.editCc ? modules.editCc : modules.edit
211
217
 
212
218
  const coreFacade = (kind, target, specifier) => ({
213
219
  kind,
@@ -275,8 +281,8 @@ export function planEntityRoutes({ entities, facts, coreRoutes, resolveFile, cac
275
281
  coreFacade('loading', `${base}/loading.tsx`, modules.loading),
276
282
  composed('page', `${base}/page.tsx`, 'createEntityListRoute', modules.list, [config, arg.template()], { acceptsTemplate: true, reexport: [{ names: ['metadata'], specifier: modules.listMetadata }] }),
277
283
  composed('page', `${base}/create/page.tsx`, 'createEntityCreateRoute', modules.create, [config, arg.template()], { acceptsTemplate: true }),
278
- composed('page', `${base}/[id]/page.tsx`, 'createEntityDetailRoute', modules.detail, [config, arg.literal(children), arg.template()], { acceptsTemplate: true, reexport: [{ names: ['metadata'], specifier: modules.detailMetadata }] }),
279
- composed('page', `${base}/[id]/edit/page.tsx`, 'createEntityEditRoute', modules.edit, [config, arg.template()], { acceptsTemplate: true })
284
+ composed('page', `${base}/[id]/page.tsx`, 'createEntityDetailRoute', detailModule, [config, arg.literal(children), arg.template()], { acceptsTemplate: true, reexport: [{ names: ['metadata'], specifier: modules.detailMetadata }] }),
285
+ composed('page', `${base}/[id]/edit/page.tsx`, 'createEntityEditRoute', editModule, [config, arg.template()], { acceptsTemplate: true })
280
286
  )
281
287
  }
282
288
 
@@ -7,6 +7,10 @@
7
7
  * before the template gained the check, or written by hand, may not have it: `nextspark prepare` (and `migrate`, in
8
8
  * the CLI) warn, naming each area the proxy never mentions.
9
9
  *
10
+ * Same for the session check (S80): a proxy copied before 0.1.0-beta.196 fetches its own /api/auth/get-session from
11
+ * the request's origin, which behind a proxy that terminates TLS is https on a plain-HTTP port, so every signed-in
12
+ * user is sent to /login. That copy is never updated by an upgrade, so prepare and migrate say what to change.
13
+ *
10
14
  * @module core/scripts/build/registry/host/proxy-areas
11
15
  */
12
16
 
@@ -24,8 +28,45 @@ export const PROTECTED_AREAS = Object.freeze([
24
28
  /** Where Next loads a project's proxy from, next to the generated src/app. */
25
29
  export const PROXY_FILES = Object.freeze(['src/proxy.ts', 'src/proxy.js', 'src/middleware.ts', 'src/middleware.js'])
26
30
 
31
+ /**
32
+ * A proxy that re-exports core's template under its own name (apps/dev does) has the check without naming the areas
33
+ * itself. Exact name (`proxy as x` is not the proxy Next loads), a concrete specifier (the package, or the repository's
34
+ * packages/core), and only code: a commented-out line does not count.
35
+ */
36
+ const REEXPORTS_CORE_PROXY =
37
+ /^\s*export\s*\{\s*(?:[\w$]+\s*,\s*)*proxy\s*(?:,[^}]*)?\}\s*from\s*['"](?:@nextsparkjs\/core|(?:\.\.\/)+packages\/core)\/templates\/proxy(?:\.ts)?['"]/m
38
+ /**
39
+ * The source without its comments (`//` to the end of the line, wherever it starts, and block comments). String
40
+ * literals are kept whole, so a `/*` or `//` inside one is text. Not parsed: regular-expression literals (a quote in
41
+ * one can hide the code after it) and template-literal `${}` nesting; nor does the session detector follow a URL
42
+ * assembled from parts. No template that copied the HTTP session check needs either.
43
+ */
44
+ export function withoutComments(source) {
45
+ let out = ''
46
+ let quote = null
47
+ for (let i = 0; i < source.length; i++) {
48
+ const c = source[i]
49
+ if (quote) {
50
+ out += c
51
+ if (c === '\\') out += source[++i] ?? ''
52
+ else if (c === quote) quote = null
53
+ } else if (c === '/' && source[i + 1] === '/') {
54
+ while (i + 1 < source.length && source[i + 1] !== '\n') i++
55
+ } else if (c === '/' && source[i + 1] === '*') {
56
+ const end = source.indexOf('*/', i + 2)
57
+ i = end < 0 ? source.length : end + 1
58
+ out += ' '
59
+ } else {
60
+ if (c === "'" || c === '"' || c === '`') quote = c
61
+ out += c
62
+ }
63
+ }
64
+ return out
65
+ }
66
+
27
67
  /** The protected areas a proxy's source never names as a path (`'/superadmin'`, `"/devtools"`, `` `/devtools` ``). */
28
68
  export function missingProxyAreas(source) {
69
+ if (REEXPORTS_CORE_PROXY.test(withoutComments(source))) return []
29
70
  return PROTECTED_AREAS.filter(area => !new RegExp(`['"\`]${area.path}(?![\\w-])`).test(source))
30
71
  }
31
72
 
@@ -46,11 +87,33 @@ export function proxyAreaNotice(file, source) {
46
87
  }
47
88
  }
48
89
 
90
+ export const PROXY_SESSION_WARNING = 'NS_PROXY_SESSION_OVER_HTTP'
91
+
92
+ /** What to write instead of the HTTP session check; migrate in the CLI says the same. */
93
+ export const PROXY_SESSION_FIX =
94
+ "import { auth } from '@nextsparkjs/core/lib/auth' and replace the betterFetch('/api/auth/get-session', ...) call with " +
95
+ "auth.api.getSession({ headers: new Headers({ cookie: request.headers.get('cookie') || '' }), query: { disableRefresh: true } }), " +
96
+ 'which returns the session itself (or null), not { data }'
97
+
98
+ /** The warning for a proxy whose code (not a comment) asks /api/auth/get-session over HTTP, or null. */
99
+ export function proxySessionNotice(file, source) {
100
+ if (!withoutComments(source).includes('/api/auth/get-session')) return null
101
+ return {
102
+ code: PROXY_SESSION_WARNING,
103
+ target: file,
104
+ message:
105
+ `${file} checks the session by fetching /api/auth/get-session from the request's own origin. Behind a proxy that ` +
106
+ `terminates TLS (X-Forwarded-Proto: https) that origin is https on a port that speaks plain HTTP: the fetch fails ` +
107
+ `and every signed-in user is sent to /login. Read the session in process, as ` +
108
+ `node_modules/@nextsparkjs/core/templates/proxy.ts does: ${PROXY_SESSION_FIX}`,
109
+ }
110
+ }
111
+
49
112
  /** The warnings for the proxy Next would load in `projectRoot` (the first of PROXY_FILES that exists). */
50
113
  export function proxyAreaNotices(projectRoot) {
51
114
  if (!projectRoot) return []
52
115
  const file = PROXY_FILES.find(candidate => existsSync(join(projectRoot, candidate)))
53
116
  if (!file) return []
54
- const notice = proxyAreaNotice(file, readFileSync(join(projectRoot, file), 'utf8'))
55
- return notice ? [notice] : []
117
+ const source = readFileSync(join(projectRoot, file), 'utf8')
118
+ return [proxyAreaNotice(file, source), proxySessionNotice(file, source)].filter(Boolean)
56
119
  }
@@ -80,11 +80,15 @@ export const CORE_COMPOSITION_WRAPPERS = Object.freeze({
80
80
  [`${INTERNAL}devtools-layout.cc`]: ['withDevtoolsGuard'],
81
81
  // Every page and layout under /superadmin and /devtools (the manifest's `access`).
82
82
  [`${INTERNAL}area-access`]: ['withSuperadminAccess', 'withDevtoolsAccess', 'withSuperadminMetadata', 'withDevtoolsMetadata', 'withSuperadminRouteAccess', 'withDevtoolsRouteAccess'],
83
+ // The same, for a Cache Components host (each segment checks inside its own Suspense boundary).
84
+ [`${INTERNAL}area-access.cc`]: ['withSuperadminAccess', 'withDevtoolsAccess', 'withSuperadminMetadata', 'withDevtoolsMetadata', 'withSuperadminRouteAccess', 'withDevtoolsRouteAccess'],
83
85
  [`${INTERNAL}entity-layout-route`]: ['createEntityLayoutRoute'],
84
86
  [`${INTERNAL}entity-list-route`]: ['createEntityListRoute'],
85
87
  [`${INTERNAL}entity-detail-route`]: ['createEntityDetailRoute'],
86
88
  [`${INTERNAL}entity-create-route`]: ['createEntityCreateRoute'],
87
89
  [`${INTERNAL}entity-edit-route`]: ['createEntityEditRoute'],
90
+ [`${INTERNAL}entity-detail-route.cc`]: ['createEntityDetailRoute'],
91
+ [`${INTERNAL}entity-edit-route.cc`]: ['createEntityEditRoute'],
88
92
  [`${INTERNAL}public-item-route`]: ['createPublicItemRoute', 'createPublicItemMetadata'],
89
93
  [`${INTERNAL}public-item-route.cc`]: ['createPublicItemRoute', 'createPublicItemMetadata'],
90
94
  [`${INTERNAL}public-archive-route`]: ['createPublicArchiveRoute', 'createPublicArchiveMetadata'],
@@ -66,6 +66,8 @@ export const VARIANT_FILES = Object.freeze({
66
66
  'superadmin/layout.cc.tsx': 'superadmin/layout.tsx',
67
67
  'devtools/layout.cc.tsx': 'devtools/layout.tsx',
68
68
  'dashboard/layout.cc.tsx': 'dashboard/layout.tsx',
69
+ 'dashboard/(main)/layout.cc.tsx': 'dashboard/(main)/layout.tsx',
70
+ '(public)/page.cc.tsx': '(public)/page.tsx',
69
71
  '(auth)/login/page.cc.tsx': '(auth)/login/page.tsx',
70
72
  '(auth)/signup/page.cc.tsx': '(auth)/signup/page.tsx',
71
73
  '(public)/docs/[section]/[page]/page.cc.tsx': '(public)/docs/[section]/[page]/page.tsx',
@@ -105,6 +107,15 @@ export const VARIANT_COMPOSE = Object.freeze({
105
107
  'devtools/layout.cc.tsx': { wrapper: 'withDevtoolsGuard', specifier: `${ROUTES_SUBPATH}/_internal/devtools-layout.cc` },
106
108
  })
107
109
 
110
+ /**
111
+ * The per-segment access check of a variant, when it differs from its base route's: in Cache Components mode every
112
+ * page and layout under an area checks the role inside its own Suspense boundary (area-access.cc).
113
+ */
114
+ export const VARIANT_ACCESS = Object.freeze({
115
+ 'superadmin/layout.cc.tsx': { ...ACCESS_ROUTES['superadmin/layout.tsx'], specifier: `${ROUTES_SUBPATH}/_internal/area-access.cc` },
116
+ 'devtools/layout.cc.tsx': { ...ACCESS_ROUTES['devtools/layout.tsx'], specifier: `${ROUTES_SUBPATH}/_internal/area-access.cc` },
117
+ })
118
+
108
119
  /**
109
120
  * Files that live next to a route without being one: the API explorer's presets, read as text by the
110
121
  * registry build (registry/discovery/api-presets.mjs), and its docs.md.
@@ -168,7 +179,8 @@ export function buildRoutesManifest(routesDir = ROUTES_DIR) {
168
179
  const replaced = manifest.find(entry => entry.target === target)
169
180
  if (!replaced) throw new Error(`Variant ${file} (${mode}) replaces ${target}, which is not a core route`)
170
181
  const compose = VARIANT_COMPOSE[file] ?? replaced.compose
171
- return { kind: replaced.kind, target, specifier: specifierForRouteFile(file), ...(compose ? { compose } : {}), ...(replaced.access ? { access: replaced.access } : {}) }
182
+ const access = VARIANT_ACCESS[file] ?? replaced.access
183
+ return { kind: replaced.kind, target, specifier: specifierForRouteFile(file), ...(compose ? { compose } : {}), ...(access ? { access } : {}) }
172
184
  })
173
185
  }
174
186
  return { manifest, variants }