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

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (456) hide show
  1. package/dist/components/api/keys/CreateApiKeyDialog.d.ts.map +1 -1
  2. package/dist/components/api/keys/CreateApiKeyDialog.js +2 -1
  3. package/dist/components/app/layouts/PublicFooter.d.ts.map +1 -1
  4. package/dist/components/app/layouts/PublicFooter.js +2 -23
  5. package/dist/components/app/misc/ThemeToggle.d.ts.map +1 -1
  6. package/dist/components/app/misc/ThemeToggle.js +2 -1
  7. package/dist/components/auth/forms/LoginForm.js +1 -1
  8. package/dist/components/auth/forms/SignupForm.d.ts.map +1 -1
  9. package/dist/components/auth/forms/SignupForm.js +2 -1
  10. package/dist/components/auth/pages/AuthErrorPage.d.ts.map +1 -1
  11. package/dist/components/auth/pages/AuthErrorPage.js +2 -1
  12. package/dist/components/billing/ManageBillingButton.d.ts.map +1 -1
  13. package/dist/components/billing/ManageBillingButton.js +2 -1
  14. package/dist/components/dashboard/block-editor/array-field.d.ts.map +1 -1
  15. package/dist/components/dashboard/block-editor/array-field.js +2 -1
  16. package/dist/components/dashboard/block-editor/block-picker.d.ts.map +1 -1
  17. package/dist/components/dashboard/block-editor/block-picker.js +2 -1
  18. package/dist/components/dashboard/block-editor/builder-editor-view.d.ts.map +1 -1
  19. package/dist/components/dashboard/block-editor/builder-editor-view.js +3 -2
  20. package/dist/components/dashboard/block-editor/config-panel.d.ts.map +1 -1
  21. package/dist/components/dashboard/block-editor/config-panel.js +3 -2
  22. package/dist/components/dashboard/block-editor/dynamic-form.d.ts.map +1 -1
  23. package/dist/components/dashboard/block-editor/dynamic-form.js +2 -1
  24. package/dist/components/dashboard/block-editor/entity-fields-sidebar.d.ts.map +1 -1
  25. package/dist/components/dashboard/block-editor/entity-fields-sidebar.js +3 -2
  26. package/dist/components/dashboard/block-editor/pattern-reference-preview.d.ts.map +1 -1
  27. package/dist/components/dashboard/block-editor/pattern-reference-preview.js +2 -1
  28. package/dist/components/dashboard/layouts/TopNavbar.d.ts.map +1 -1
  29. package/dist/components/dashboard/layouts/TopNavbar.js +4 -3
  30. package/dist/components/dashboard/misc/SearchDropdown.d.ts.map +1 -1
  31. package/dist/components/dashboard/misc/SearchDropdown.js +2 -1
  32. package/dist/components/dashboard/mobile/MobileTopBar.d.ts.map +1 -1
  33. package/dist/components/dashboard/mobile/MobileTopBar.js +2 -1
  34. package/dist/components/devtools/BlockDetailViewer.d.ts.map +1 -1
  35. package/dist/components/devtools/BlockDetailViewer.js +2 -1
  36. package/dist/components/devtools/BlocksViewer.d.ts.map +1 -1
  37. package/dist/components/devtools/BlocksViewer.js +2 -1
  38. package/dist/components/devtools/ConfigViewer.d.ts.map +1 -1
  39. package/dist/components/devtools/ConfigViewer.js +3 -2
  40. package/dist/components/devtools/TestCasesViewer.d.ts.map +1 -1
  41. package/dist/components/devtools/TestCasesViewer.js +3 -2
  42. package/dist/components/devtools/TestCoverageDashboard.d.ts.map +1 -1
  43. package/dist/components/devtools/TestCoverageDashboard.js +2 -1
  44. package/dist/components/devtools/api-explorer/ApiDocsModal.d.ts.map +1 -1
  45. package/dist/components/devtools/api-explorer/ApiDocsModal.js +0 -0
  46. package/dist/components/devtools/api-explorer/ApiExplorer.d.ts.map +1 -1
  47. package/dist/components/devtools/api-explorer/ApiExplorer.js +2 -1
  48. package/dist/components/devtools/scheduled-actions/actions-table.d.ts.map +1 -1
  49. package/dist/components/devtools/scheduled-actions/actions-table.js +3 -2
  50. package/dist/components/docs/docs-sidebar.d.ts.map +1 -1
  51. package/dist/components/docs/docs-sidebar.js +3 -2
  52. package/dist/components/entities/EntityFieldRenderer.d.ts.map +1 -1
  53. package/dist/components/entities/EntityFieldRenderer.js +2 -1
  54. package/dist/components/entities/EntityList.d.ts.map +1 -1
  55. package/dist/components/entities/EntityList.js +2 -1
  56. package/dist/components/entities/EntityTable.d.ts.map +1 -1
  57. package/dist/components/entities/EntityTable.js +3 -2
  58. package/dist/components/entities/ErrorBoundary.d.ts.map +1 -1
  59. package/dist/components/entities/ErrorBoundary.js +2 -1
  60. package/dist/components/entities/wrappers/EntityDetailWrapper.d.ts.map +1 -1
  61. package/dist/components/entities/wrappers/EntityDetailWrapper.js +3 -2
  62. package/dist/components/media/MediaCard.d.ts.map +1 -1
  63. package/dist/components/media/MediaCard.js +2 -1
  64. package/dist/components/media/MediaDetailPanel.d.ts.map +1 -1
  65. package/dist/components/media/MediaDetailPanel.js +2 -1
  66. package/dist/components/media/MediaList.d.ts.map +1 -1
  67. package/dist/components/media/MediaList.js +2 -1
  68. package/dist/components/media/MediaSelector.d.ts.map +1 -1
  69. package/dist/components/media/MediaSelector.js +2 -1
  70. package/dist/components/media/MediaUploadZone.d.ts.map +1 -1
  71. package/dist/components/media/MediaUploadZone.js +2 -1
  72. package/dist/components/public/entities/PublicEntityDetail.d.ts.map +1 -1
  73. package/dist/components/public/entities/PublicEntityDetail.js +2 -1
  74. package/dist/components/teams/TeamPendingInvitations.d.ts.map +1 -1
  75. package/dist/components/teams/TeamPendingInvitations.js +3 -2
  76. package/dist/components/ui/accordion.js +1 -1
  77. package/dist/components/ui/audio-upload.d.ts.map +1 -1
  78. package/dist/components/ui/audio-upload.js +2 -1
  79. package/dist/components/ui/avatar.d.ts +17 -1
  80. package/dist/components/ui/avatar.d.ts.map +1 -1
  81. package/dist/components/ui/avatar.js +29 -2
  82. package/dist/components/ui/badge.js +1 -1
  83. package/dist/components/ui/breadcrumb.d.ts +13 -0
  84. package/dist/components/ui/breadcrumb.d.ts.map +1 -1
  85. package/dist/components/ui/breadcrumb.js +23 -10
  86. package/dist/components/ui/button.js +1 -1
  87. package/dist/components/ui/card.js +1 -1
  88. package/dist/components/ui/checkbox.js +1 -1
  89. package/dist/components/ui/file-upload.d.ts.map +1 -1
  90. package/dist/components/ui/file-upload.js +2 -1
  91. package/dist/components/ui/image-upload.d.ts.map +1 -1
  92. package/dist/components/ui/image-upload.js +4 -3
  93. package/dist/components/ui/input.js +1 -1
  94. package/dist/components/ui/label.js +1 -1
  95. package/dist/components/ui/optimized-image.d.ts.map +1 -1
  96. package/dist/components/ui/optimized-image.js +5 -0
  97. package/dist/components/ui/pagination.d.ts +1 -1
  98. package/dist/components/ui/pagination.d.ts.map +1 -1
  99. package/dist/components/ui/pagination.js +3 -0
  100. package/dist/components/ui/progress.js +1 -1
  101. package/dist/components/ui/relation-display.d.ts.map +1 -1
  102. package/dist/components/ui/relation-display.js +3 -2
  103. package/dist/components/ui/separator.js +1 -1
  104. package/dist/components/ui/simple-relation-select.d.ts.map +1 -1
  105. package/dist/components/ui/simple-relation-select.js +7 -6
  106. package/dist/components/ui/skeleton.js +1 -1
  107. package/dist/components/ui/slider.js +1 -1
  108. package/dist/components/ui/switch.js +1 -1
  109. package/dist/components/ui/tabs.js +1 -1
  110. package/dist/components/ui/textarea.js +1 -1
  111. package/dist/components/ui/user-display.d.ts.map +1 -1
  112. package/dist/components/ui/user-display.js +2 -1
  113. package/dist/components/ui/user-select.d.ts.map +1 -1
  114. package/dist/components/ui/user-select.js +2 -1
  115. package/dist/components/ui/video-upload.d.ts.map +1 -1
  116. package/dist/components/ui/video-upload.js +3 -2
  117. package/dist/components/users/tables/UsersTable.d.ts.map +1 -1
  118. package/dist/components/users/tables/UsersTable.js +3 -2
  119. package/dist/contexts/SubscriptionContext.d.ts.map +1 -1
  120. package/dist/contexts/SubscriptionContext.js +4 -3
  121. package/dist/contexts/TeamContext.d.ts.map +1 -1
  122. package/dist/contexts/TeamContext.js +3 -2
  123. package/dist/hooks/useAccountPreferencesSync.d.ts.map +1 -1
  124. package/dist/hooks/useAccountPreferencesSync.js +2 -1
  125. package/dist/hooks/useApiExplorerNavigation.d.ts.map +1 -1
  126. package/dist/hooks/useApiExplorerNavigation.js +2 -1
  127. package/dist/hooks/useAuth.d.ts.map +1 -1
  128. package/dist/hooks/useAuth.js +3 -2
  129. package/dist/hooks/useEnsureUserMetadata.d.ts.map +1 -1
  130. package/dist/hooks/useEnsureUserMetadata.js +3 -2
  131. package/dist/hooks/useEntityWithMeta.d.ts.map +1 -1
  132. package/dist/hooks/useEntityWithMeta.js +3 -2
  133. package/dist/hooks/useInvoice.d.ts.map +1 -1
  134. package/dist/hooks/useInvoice.js +2 -1
  135. package/dist/hooks/useInvoices.d.ts.map +1 -1
  136. package/dist/hooks/useInvoices.js +2 -1
  137. package/dist/hooks/useMedia.d.ts.map +1 -1
  138. package/dist/hooks/useMedia.js +10 -9
  139. package/dist/hooks/useMediaUpload.d.ts.map +1 -1
  140. package/dist/hooks/useMediaUpload.js +2 -1
  141. package/dist/hooks/useMembership.d.ts.map +1 -1
  142. package/dist/hooks/useMembership.js +3 -2
  143. package/dist/hooks/usePatternUsages.d.ts.map +1 -1
  144. package/dist/hooks/usePatternUsages.js +3 -2
  145. package/dist/hooks/usePrefetch.d.ts.map +1 -1
  146. package/dist/hooks/usePrefetch.js +3 -2
  147. package/dist/hooks/useQuota.d.ts.map +1 -1
  148. package/dist/hooks/useQuota.js +2 -1
  149. package/dist/hooks/useTeamInvitations.d.ts.map +1 -1
  150. package/dist/hooks/useTeamInvitations.js +4 -3
  151. package/dist/hooks/useTeamMembers.d.ts.map +1 -1
  152. package/dist/hooks/useTeamMembers.js +5 -4
  153. package/dist/hooks/useTeamUpdate.d.ts.map +1 -1
  154. package/dist/hooks/useTeamUpdate.js +2 -1
  155. package/dist/hooks/useUserProfile.d.ts.map +1 -1
  156. package/dist/hooks/useUserProfile.js +3 -2
  157. package/dist/hooks/useUserSettings.d.ts.map +1 -1
  158. package/dist/hooks/useUserSettings.js +3 -2
  159. package/dist/lib/actions/entity.actions.d.ts.map +1 -1
  160. package/dist/lib/actions/entity.actions.js +2 -1
  161. package/dist/lib/api/entities.d.ts.map +1 -1
  162. package/dist/lib/api/entities.js +4 -3
  163. package/dist/lib/auth/verify-email-link.d.ts +10 -0
  164. package/dist/lib/auth/verify-email-link.d.ts.map +1 -0
  165. package/dist/lib/auth/verify-email-link.js +14 -0
  166. package/dist/lib/auth-client.d.ts +2 -0
  167. package/dist/lib/auth-client.d.ts.map +1 -1
  168. package/dist/lib/auth-client.js +4 -0
  169. package/dist/lib/auth.d.ts.map +1 -1
  170. package/dist/lib/auth.js +10 -2
  171. package/dist/lib/base-path.d.ts +85 -0
  172. package/dist/lib/base-path.d.ts.map +1 -0
  173. package/dist/lib/base-path.js +79 -0
  174. package/dist/lib/billing/schema.d.ts +1 -1
  175. package/dist/lib/billing/schema.d.ts.map +1 -1
  176. package/dist/lib/billing/schema.js +1 -1
  177. package/dist/lib/blocks/sanitize-html.d.ts.map +1 -1
  178. package/dist/lib/blocks/sanitize-html.js +18 -2
  179. package/dist/lib/cache/revalidate-tag.d.ts +14 -0
  180. package/dist/lib/cache/revalidate-tag.d.ts.map +1 -0
  181. package/dist/lib/cache/revalidate-tag.js +8 -0
  182. package/dist/lib/config/dashboard-schema.d.ts +1 -1
  183. package/dist/lib/config/dashboard-schema.d.ts.map +1 -1
  184. package/dist/lib/config/dashboard-schema.js +1 -1
  185. package/dist/lib/config/types.d.ts +36 -16
  186. package/dist/lib/config/types.d.ts.map +1 -1
  187. package/dist/lib/docs/access.d.ts +28 -0
  188. package/dist/lib/docs/access.d.ts.map +1 -0
  189. package/dist/lib/docs/access.js +16 -0
  190. package/dist/lib/docs/parser.d.ts.map +1 -1
  191. package/dist/lib/docs/parser.js +2 -1
  192. package/dist/lib/docs/remark-doc-links.d.ts +14 -0
  193. package/dist/lib/docs/remark-doc-links.d.ts.map +1 -0
  194. package/dist/lib/docs/remark-doc-links.js +38 -0
  195. package/dist/lib/entities/api-generator.d.ts +1 -1
  196. package/dist/lib/entities/api-generator.d.ts.map +1 -1
  197. package/dist/lib/entities/api-generator.js +1 -1
  198. package/dist/lib/entities/schema-generator.d.ts +1 -1
  199. package/dist/lib/entities/schema-generator.d.ts.map +1 -1
  200. package/dist/lib/entities/schema-generator.js +1 -1
  201. package/dist/lib/mcp/engine.js +1 -1
  202. package/dist/lib/mcp/schema-builder.d.ts +1 -1
  203. package/dist/lib/mcp/schema-builder.d.ts.map +1 -1
  204. package/dist/lib/mcp/schema-builder.js +1 -1
  205. package/dist/lib/mcp/tool-generator.js +1 -1
  206. package/dist/lib/mcp/types.d.ts +1 -1
  207. package/dist/lib/mcp/types.d.ts.map +1 -1
  208. package/dist/lib/media/schemas.d.ts +1 -1
  209. package/dist/lib/media/schemas.d.ts.map +1 -1
  210. package/dist/lib/media/schemas.js +1 -1
  211. package/dist/lib/middleware/index.d.ts +5 -1
  212. package/dist/lib/middleware/index.d.ts.map +1 -1
  213. package/dist/lib/middleware/index.js +3 -2
  214. package/dist/lib/services/meta.service.d.ts +60 -0
  215. package/dist/lib/services/meta.service.d.ts.map +1 -1
  216. package/dist/lib/services/meta.service.js +132 -12
  217. package/dist/lib/services/middleware.service.d.ts.map +1 -1
  218. package/dist/lib/services/middleware.service.js +3 -2
  219. package/dist/lib/teams/schema.d.ts +1 -1
  220. package/dist/lib/teams/schema.d.ts.map +1 -1
  221. package/dist/lib/teams/schema.js +1 -1
  222. package/dist/lib/teams/teams.api.d.ts.map +1 -1
  223. package/dist/lib/teams/teams.api.js +4 -3
  224. package/dist/lib/user-data-client.d.ts.map +1 -1
  225. package/dist/lib/user-data-client.js +3 -2
  226. package/dist/lib/validation/invoices.d.ts +1 -1
  227. package/dist/lib/validation/invoices.d.ts.map +1 -1
  228. package/dist/lib/validation/invoices.js +1 -1
  229. package/dist/lib/validation-schemas.d.ts +1 -1
  230. package/dist/lib/validation-schemas.d.ts.map +1 -1
  231. package/dist/lib/validation-schemas.js +1 -1
  232. package/dist/lib/validation.d.ts +1 -1
  233. package/dist/lib/validation.d.ts.map +1 -1
  234. package/dist/lib/validation.js +1 -1
  235. package/dist/messages/de/footer.json +0 -1
  236. package/dist/messages/de/index.d.ts +0 -1
  237. package/dist/messages/de/index.d.ts.map +1 -1
  238. package/dist/messages/en/footer.json +0 -1
  239. package/dist/messages/en/index.d.ts +0 -1
  240. package/dist/messages/en/index.d.ts.map +1 -1
  241. package/dist/messages/es/footer.json +0 -1
  242. package/dist/messages/es/index.d.ts +0 -1
  243. package/dist/messages/es/index.d.ts.map +1 -1
  244. package/dist/messages/fr/footer.json +0 -1
  245. package/dist/messages/fr/index.d.ts +0 -1
  246. package/dist/messages/fr/index.d.ts.map +1 -1
  247. package/dist/messages/it/footer.json +0 -1
  248. package/dist/messages/it/index.d.ts +0 -1
  249. package/dist/messages/it/index.d.ts.map +1 -1
  250. package/dist/messages/pt/footer.json +0 -1
  251. package/dist/messages/pt/index.d.ts +0 -1
  252. package/dist/messages/pt/index.d.ts.map +1 -1
  253. package/dist/templates/app/(auth)/accept-invite/[token]/page.tsx +4 -3
  254. package/dist/templates/app/(auth)/verify-email/page.tsx +4 -3
  255. package/dist/templates/app/(public)/docs/[section]/[page]/page.tsx +11 -0
  256. package/dist/templates/app/api/auth/[...all]/route.ts +15 -14
  257. package/dist/templates/app/api/internal/user-metadata/route.ts +6 -8
  258. package/dist/templates/app/api/user/plan-flags/route.ts +1 -1
  259. package/dist/templates/app/api/user/profile/route.ts +12 -17
  260. package/dist/templates/app/api/v1/api-keys/route.ts +1 -1
  261. package/dist/templates/app/api/v1/auth/signup-with-invite/route.ts +2 -1
  262. package/dist/templates/app/api/v1/billing/cancel/route.ts +1 -1
  263. package/dist/templates/app/api/v1/billing/change-plan/route.ts +1 -1
  264. package/dist/templates/app/api/v1/billing/check-action/route.ts +1 -1
  265. package/dist/templates/app/api/v1/billing/checkout/route.ts +1 -1
  266. package/dist/templates/app/api/v1/blocks/validate/route.ts +1 -1
  267. package/dist/templates/app/api/v1/media/[id]/tags/route.ts +1 -1
  268. package/dist/templates/app/api/v1/post-categories/[id]/route.ts +1 -1
  269. package/dist/templates/app/api/v1/post-categories/route.ts +1 -1
  270. package/dist/templates/app/api/v1/teams/[teamId]/members/route.ts +2 -1
  271. package/dist/templates/app/api/v1/teams/[teamId]/subscription/route.ts +3 -5
  272. package/dist/templates/app/api/v1/teams/docs.md +4 -9
  273. package/dist/templates/app/api/v1/teams/switch/route.ts +1 -1
  274. package/dist/templates/app/api/v1/users/[id]/meta/[key]/route.ts +1 -1
  275. package/dist/templates/app/api/v1/users/[id]/route.ts +1 -1
  276. package/dist/templates/app/api/v1/users/route.ts +1 -1
  277. package/dist/templates/app/dashboard/(main)/media/page.tsx +3 -1
  278. package/dist/templates/app/dashboard/settings/api-keys/page.tsx +5 -4
  279. package/dist/templates/app/dashboard/settings/profile/page.tsx +3 -2
  280. package/dist/templates/app/devtools/scheduled-actions/page.tsx +2 -1
  281. package/dist/templates/app/superadmin/docs/[section]/[page]/page.tsx +16 -0
  282. package/dist/templates/app/superadmin/subscriptions/page.tsx +3 -2
  283. package/dist/templates/app/superadmin/teams/[teamId]/page.tsx +5 -4
  284. package/dist/templates/app/superadmin/users/[userId]/page.tsx +2 -1
  285. package/dist/templates/app/superadmin/users/page.tsx +2 -1
  286. package/dist/templates/blocks/cta-section/component.tsx +3 -2
  287. package/dist/templates/blocks/cta-section/schema.ts +1 -1
  288. package/dist/templates/blocks/features-grid/component.tsx +2 -1
  289. package/dist/templates/blocks/features-grid/schema.ts +1 -1
  290. package/dist/templates/blocks/hero/component.tsx +3 -2
  291. package/dist/templates/blocks/hero/schema.ts +1 -1
  292. package/dist/templates/blocks/testimonials/component.tsx +2 -1
  293. package/dist/templates/blocks/testimonials/schema.ts +1 -1
  294. package/dist/templates/blocks/text-content/component.tsx +2 -1
  295. package/dist/templates/blocks/text-content/schema.ts +1 -1
  296. package/dist/templates/ci-workflows/README.md +1 -1
  297. package/dist/templates/ci-workflows/github/cypress-regression.yml +1 -1
  298. package/dist/templates/ci-workflows/github/cypress-smoke.yml +1 -1
  299. package/dist/templates/contents/plugins/starter/plugin/api/example/route.ts.template +1 -1
  300. package/dist/templates/contents/themes/starter/blocks/cta-section/component.tsx +3 -2
  301. package/dist/templates/contents/themes/starter/blocks/cta-section/schema.ts +1 -1
  302. package/dist/templates/contents/themes/starter/blocks/features-grid/component.tsx +2 -1
  303. package/dist/templates/contents/themes/starter/blocks/features-grid/schema.ts +1 -1
  304. package/dist/templates/contents/themes/starter/blocks/hero/component.tsx +3 -2
  305. package/dist/templates/contents/themes/starter/blocks/hero/schema.ts +1 -1
  306. package/dist/templates/contents/themes/starter/blocks/testimonials/component.tsx +3 -2
  307. package/dist/templates/contents/themes/starter/blocks/testimonials/schema.ts +1 -1
  308. package/dist/templates/contents/themes/starter/blocks/text-content/component.tsx +2 -1
  309. package/dist/templates/contents/themes/starter/blocks/text-content/schema.ts +1 -1
  310. package/dist/templates/contents/themes/starter/entities/posts/components/post-header.tsx +2 -1
  311. package/dist/templates/contents/themes/starter/lib/hooks/useAnalyticsData.ts +2 -1
  312. package/dist/templates/contents/themes/starter/lib/hooks/useTaskStats.ts +2 -1
  313. package/dist/templates/eslint.config.mjs +133 -9
  314. package/dist/templates/features/blog/blocks/post-content/component.tsx +2 -1
  315. package/dist/templates/features/blog/blocks/post-content/schema.ts +1 -1
  316. package/dist/templates/features/blog/entities/posts/components/post-header.tsx +2 -1
  317. package/dist/templates/features/pages/blocks/hero/component.tsx +3 -2
  318. package/dist/templates/features/pages/blocks/hero/schema.ts +1 -1
  319. package/dist/templates/next.config.mjs +50 -29
  320. package/dist/templates/proxy.ts +100 -21
  321. package/dist/templates/public/sw.js +2 -2
  322. package/dist/templates/scripts/cy-run-prod.cjs +5 -5
  323. package/dist/types/blocks.d.ts +1 -1
  324. package/dist/types/blocks.d.ts.map +1 -1
  325. package/dist/types/blocks.js +1 -1
  326. package/package.json +9 -5
  327. package/scripts/build/docs-registry.mjs +7 -7
  328. package/scripts/build/registry/__tests__/billing-registry.test.mjs +43 -0
  329. package/scripts/build/registry/__tests__/build-output.test.mjs +209 -0
  330. package/scripts/build/registry/__tests__/copy-race-child.mjs +14 -0
  331. package/scripts/build/registry/__tests__/direct-fs-writes.mjs +203 -0
  332. package/scripts/build/registry/__tests__/globals-css-tag.test.mjs +66 -0
  333. package/scripts/build/registry/__tests__/icons-discovery.test.mjs +530 -29
  334. package/scripts/build/registry/__tests__/page-generator.test.mjs +211 -37
  335. package/scripts/build/registry/__tests__/published-signatures.test.mjs +239 -0
  336. package/scripts/build/registry/__tests__/route-file-rule.test.mjs +170 -0
  337. package/scripts/build/registry/__tests__/safe-fs.test.mjs +333 -0
  338. package/scripts/build/registry/__tests__/template-analysis.test.mjs +142 -0
  339. package/scripts/build/registry/__tests__/template-registry.test.mjs +279 -9
  340. package/scripts/build/registry/__tests__/templates-plan.test.mjs +128 -0
  341. package/scripts/build/registry/__tests__/templates-tree.test.mjs +367 -0
  342. package/scripts/build/registry/__tests__/write-places.test.mjs +425 -0
  343. package/scripts/build/registry/config.mjs +6 -65
  344. package/scripts/build/registry/discovery/icons.mjs +498 -43
  345. package/scripts/build/registry/discovery/templates.mjs +1 -1
  346. package/scripts/build/registry/generators/billing-registry.mjs +1 -1
  347. package/scripts/build/registry/generators/docs-registry.mjs +3 -4
  348. package/scripts/build/registry/generators/feature-registry.mjs +1 -1
  349. package/scripts/build/registry/generators/permissions-registry.mjs +2 -2
  350. package/scripts/build/registry/generators/template-registry.mjs +65 -49
  351. package/scripts/build/registry/index.mjs +1 -2
  352. package/scripts/build/registry/post-build/index.mjs +1 -1
  353. package/scripts/build/registry/post-build/own-gitignores.mjs +220 -0
  354. package/scripts/build/registry/post-build/page-generator.mjs +512 -268
  355. package/scripts/build/registry/post-build/route-cleanup.mjs +16 -13
  356. package/scripts/build/registry/post-build/test-fixtures.mjs +26 -8
  357. package/scripts/build/registry/project-mode.mjs +76 -0
  358. package/scripts/build/registry/shared/typescript-compiler.mjs +77 -0
  359. package/scripts/build/registry/validate-env.test.mjs +12 -8
  360. package/scripts/build/registry/watch.mjs +3 -53
  361. package/scripts/build/registry/write-places.mjs +228 -0
  362. package/scripts/build/registry.mjs +53 -30
  363. package/scripts/build/safe-fs.mjs +279 -0
  364. package/scripts/build/templates-plan.mjs +44 -0
  365. package/scripts/build/theme.mjs +46 -42
  366. package/scripts/build/update-tsconfig.mjs +16 -15
  367. package/scripts/db/cluster-changes.mjs +205 -0
  368. package/scripts/db/connection-time-limits.mjs +53 -0
  369. package/scripts/db/inspect-server.mjs +108 -0
  370. package/scripts/db/migration-time-limit.mjs +695 -0
  371. package/scripts/db/run-migrations.mjs +46 -41
  372. package/scripts/db/ssl-config.mjs +194 -0
  373. package/scripts/db/theme-location.mjs +69 -0
  374. package/scripts/db/verify-tables.mjs +3 -8
  375. package/scripts/db/verify-teams.mjs +2 -6
  376. package/scripts/db/verify-theme-migrations.mjs +169 -0
  377. package/scripts/maintenance/core-updater.mjs +1003 -0
  378. package/scripts/maintenance/step-guard.mjs +74 -0
  379. package/scripts/maintenance/update-core.mjs +14 -831
  380. package/scripts/utils/console-guard.mjs +13 -0
  381. package/scripts/utils/generated-tag.mjs +66 -0
  382. package/scripts/utils/index.mjs +9 -1
  383. package/scripts/utils/logging.mjs +140 -0
  384. package/templates/app/(auth)/accept-invite/[token]/page.tsx +4 -3
  385. package/templates/app/(auth)/verify-email/page.tsx +4 -3
  386. package/templates/app/(public)/docs/[section]/[page]/page.tsx +11 -0
  387. package/templates/app/api/auth/[...all]/route.ts +15 -14
  388. package/templates/app/api/internal/user-metadata/route.ts +6 -8
  389. package/templates/app/api/user/plan-flags/route.ts +1 -1
  390. package/templates/app/api/user/profile/route.ts +12 -17
  391. package/templates/app/api/v1/api-keys/route.ts +1 -1
  392. package/templates/app/api/v1/auth/signup-with-invite/route.ts +2 -1
  393. package/templates/app/api/v1/billing/cancel/route.ts +1 -1
  394. package/templates/app/api/v1/billing/change-plan/route.ts +1 -1
  395. package/templates/app/api/v1/billing/check-action/route.ts +1 -1
  396. package/templates/app/api/v1/billing/checkout/route.ts +1 -1
  397. package/templates/app/api/v1/blocks/validate/route.ts +1 -1
  398. package/templates/app/api/v1/media/[id]/tags/route.ts +1 -1
  399. package/templates/app/api/v1/post-categories/[id]/route.ts +1 -1
  400. package/templates/app/api/v1/post-categories/route.ts +1 -1
  401. package/templates/app/api/v1/teams/[teamId]/members/route.ts +2 -1
  402. package/templates/app/api/v1/teams/[teamId]/subscription/route.ts +3 -5
  403. package/templates/app/api/v1/teams/docs.md +4 -9
  404. package/templates/app/api/v1/teams/switch/route.ts +1 -1
  405. package/templates/app/api/v1/users/[id]/meta/[key]/route.ts +1 -1
  406. package/templates/app/api/v1/users/[id]/route.ts +1 -1
  407. package/templates/app/api/v1/users/route.ts +1 -1
  408. package/templates/app/dashboard/(main)/media/page.tsx +3 -1
  409. package/templates/app/dashboard/settings/api-keys/page.tsx +5 -4
  410. package/templates/app/dashboard/settings/profile/page.tsx +3 -2
  411. package/templates/app/devtools/scheduled-actions/page.tsx +2 -1
  412. package/templates/app/superadmin/docs/[section]/[page]/page.tsx +16 -0
  413. package/templates/app/superadmin/subscriptions/page.tsx +3 -2
  414. package/templates/app/superadmin/teams/[teamId]/page.tsx +5 -4
  415. package/templates/app/superadmin/users/[userId]/page.tsx +2 -1
  416. package/templates/app/superadmin/users/page.tsx +2 -1
  417. package/templates/blocks/cta-section/component.tsx +3 -2
  418. package/templates/blocks/cta-section/schema.ts +1 -1
  419. package/templates/blocks/features-grid/component.tsx +2 -1
  420. package/templates/blocks/features-grid/schema.ts +1 -1
  421. package/templates/blocks/hero/component.tsx +3 -2
  422. package/templates/blocks/hero/schema.ts +1 -1
  423. package/templates/blocks/testimonials/component.tsx +2 -1
  424. package/templates/blocks/testimonials/schema.ts +1 -1
  425. package/templates/blocks/text-content/component.tsx +2 -1
  426. package/templates/blocks/text-content/schema.ts +1 -1
  427. package/templates/ci-workflows/README.md +1 -1
  428. package/templates/ci-workflows/github/cypress-regression.yml +1 -1
  429. package/templates/ci-workflows/github/cypress-smoke.yml +1 -1
  430. package/templates/contents/plugins/starter/plugin/api/example/route.ts.template +1 -1
  431. package/templates/contents/themes/starter/blocks/cta-section/component.tsx +3 -2
  432. package/templates/contents/themes/starter/blocks/cta-section/schema.ts +1 -1
  433. package/templates/contents/themes/starter/blocks/features-grid/component.tsx +2 -1
  434. package/templates/contents/themes/starter/blocks/features-grid/schema.ts +1 -1
  435. package/templates/contents/themes/starter/blocks/hero/component.tsx +3 -2
  436. package/templates/contents/themes/starter/blocks/hero/schema.ts +1 -1
  437. package/templates/contents/themes/starter/blocks/testimonials/component.tsx +3 -2
  438. package/templates/contents/themes/starter/blocks/testimonials/schema.ts +1 -1
  439. package/templates/contents/themes/starter/blocks/text-content/component.tsx +2 -1
  440. package/templates/contents/themes/starter/blocks/text-content/schema.ts +1 -1
  441. package/templates/contents/themes/starter/entities/posts/components/post-header.tsx +2 -1
  442. package/templates/contents/themes/starter/lib/hooks/useAnalyticsData.ts +2 -1
  443. package/templates/contents/themes/starter/lib/hooks/useTaskStats.ts +2 -1
  444. package/templates/eslint.config.mjs +133 -9
  445. package/templates/features/blog/blocks/post-content/component.tsx +2 -1
  446. package/templates/features/blog/blocks/post-content/schema.ts +1 -1
  447. package/templates/features/blog/entities/posts/components/post-header.tsx +2 -1
  448. package/templates/features/pages/blocks/hero/component.tsx +3 -2
  449. package/templates/features/pages/blocks/hero/schema.ts +1 -1
  450. package/templates/next.config.mjs +50 -29
  451. package/templates/proxy.ts +100 -21
  452. package/templates/public/sw.js +2 -2
  453. package/templates/scripts/cy-run-prod.cjs +5 -5
  454. package/tests/jest/__mocks__/@nextsparkjs/registries/template-registry.client.ts +11 -0
  455. package/tests/jest/__mocks__/next-server.js +10 -0
  456. package/scripts/build/docs.mjs +0 -392
@@ -1,9 +1,7 @@
1
- import pg from "pg";
2
1
  import fs from "fs";
3
2
  import path from "path";
4
3
  import { fileURLToPath } from 'url';
5
-
6
- const { Client } = pg;
4
+ import { migrationTimeLimit, migrationClient, ignoredParametersNotice, runMigrationSql, recordMigration, migrationFailure } from './migration-time-limit.mjs';
7
5
 
8
6
  const __filename = fileURLToPath(import.meta.url);
9
7
  const __dirname = path.dirname(__filename);
@@ -17,8 +15,11 @@ const packageRoot = path.join(__dirname, '..', '..'); // scripts/db/ -> core/
17
15
  const isMonorepoMode = fs.existsSync(path.join(projectRoot, 'packages', 'core'));
18
16
  const rootDir = projectRoot; // For backward compatibility with code below
19
17
 
20
- // Read environment variables: prefer .env file if it exists, fallback to process.env (e.g. Vercel/CI)
18
+ // Read environment variables: prefer .env file if it exists, fallback to process.env (e.g. Vercel/CI).
19
+ // `--no-env-file` skips the .env file, so the connection and theme come only from the
20
+ // environment this process was started with (db:verify-theme runs it that way).
21
21
  const envPath = path.join(projectRoot, '.env');
22
+ const readEnvFile = !process.argv.includes('--no-env-file');
22
23
 
23
24
  let DATABASE_URL = process.env.DATABASE_URL ?? null;
24
25
  // Migrations and seeds run as the table OWNER. After the runtime cutover the app
@@ -27,8 +28,9 @@ let DATABASE_URL = process.env.DATABASE_URL ?? null;
27
28
  // Falls back to DATABASE_URL when unset (pre-cutover: same owner connection).
28
29
  let MIGRATE_DATABASE_URL = process.env.MIGRATE_DATABASE_URL ?? null;
29
30
  let ACTIVE_THEME = process.env.NEXT_PUBLIC_ACTIVE_THEME ?? null;
31
+ let MIGRATION_TIMEOUT_SECONDS = process.env.MIGRATION_TIMEOUT_SECONDS;
30
32
 
31
- if (fs.existsSync(envPath)) {
33
+ if (readEnvFile && fs.existsSync(envPath)) {
32
34
  const envContent = fs.readFileSync(envPath, 'utf8');
33
35
  const envLines = envContent.split('\n');
34
36
 
@@ -46,6 +48,9 @@ if (fs.existsSync(envPath)) {
46
48
  if (key?.trim() === 'NEXT_PUBLIC_ACTIVE_THEME' && valueParts.length > 0) {
47
49
  ACTIVE_THEME = value;
48
50
  }
51
+ if (key?.trim() === 'MIGRATION_TIMEOUT_SECONDS' && valueParts.length > 0) {
52
+ MIGRATION_TIMEOUT_SECONDS = value;
53
+ }
49
54
  }
50
55
  });
51
56
  }
@@ -63,15 +68,21 @@ if (!ACTIVE_THEME) {
63
68
  process.exit(1);
64
69
  }
65
70
 
71
+ // How long each migration may run, when MIGRATION_TIMEOUT_SECONDS asks for a limit
72
+ // (see migration-time-limit.mjs). Without it, a migration runs for as long as it takes.
73
+ let TIME_LIMIT;
74
+ try {
75
+ TIME_LIMIT = migrationTimeLimit({ MIGRATION_TIMEOUT_SECONDS });
76
+ } catch (error) {
77
+ console.error(`❌ ${error.message}`);
78
+ process.exit(1);
79
+ }
80
+
81
+ const ignoredParameters = ignoredParametersNotice(MIGRATION_URL, TIME_LIMIT);
82
+ if (ignoredParameters) console.log(`⚠️ ${ignoredParameters}\n`);
83
+
66
84
  async function runMigrations() {
67
- const client = new Client({
68
- connectionString: MIGRATION_URL,
69
- ssl: {
70
- rejectUnauthorized: false,
71
- require: true
72
- },
73
- connectionTimeoutMillis: 10000
74
- });
85
+ const client = migrationClient(MIGRATION_URL, TIME_LIMIT);
75
86
 
76
87
  try {
77
88
  await client.connect();
@@ -113,17 +124,14 @@ async function runMigrations() {
113
124
  const sql = fs.readFileSync(path.join(migrationsDir, file), 'utf8');
114
125
 
115
126
  try {
116
- await client.query(sql);
127
+ await runMigrationSql(client, { sql, limit: TIME_LIMIT, connectionString: MIGRATION_URL });
117
128
 
118
129
  // Record successful migration
119
- await client.query(
120
- 'INSERT INTO "_migrations" (filename) VALUES ($1)',
121
- [file]
122
- );
130
+ await recordMigration(client, { file, table: '_migrations', row: { filename: file }, limit: TIME_LIMIT });
123
131
 
124
132
  console.log(`✅ Successfully executed ${file}\n`);
125
133
  } catch (error) {
126
- console.error(`❌ Failed to execute ${file}:`, error.message);
134
+ console.error(`❌ ${migrationFailure(file, error)}`);
127
135
  throw error;
128
136
  }
129
137
  }
@@ -328,11 +336,13 @@ async function executeContentMigration(client, migration) {
328
336
  console.log(` 🔄 ${filename}...`);
329
337
  const sql = fs.readFileSync(fullPath, 'utf8');
330
338
 
331
- await client.query(sql);
332
- await client.query(
333
- 'INSERT INTO "_content_migrations" (source_type, source_name, filename) VALUES ($1, $2, $3)',
334
- [sourceType, sourceName, filename]
335
- );
339
+ await runMigrationSql(client, { sql, limit: TIME_LIMIT, connectionString: MIGRATION_URL });
340
+ await recordMigration(client, {
341
+ file: filename,
342
+ table: '_content_migrations',
343
+ row: { source_type: sourceType, source_name: sourceName, filename },
344
+ limit: TIME_LIMIT,
345
+ });
336
346
 
337
347
  console.log(` ✅ ${filename} executed successfully`);
338
348
  return true;
@@ -357,11 +367,13 @@ async function executeEntityMigration(client, migration) {
357
367
  console.log(`${indent}🔄 ${filename}...`);
358
368
  const sql = fs.readFileSync(fullPath, 'utf8');
359
369
 
360
- await client.query(sql);
361
- await client.query(
362
- 'INSERT INTO "_entity_migrations" (entity_name, source_type, source_name, filename) VALUES ($1, $2, $3, $4)',
363
- [entityName, sourceType, sourceName, filename]
364
- );
370
+ await runMigrationSql(client, { sql, limit: TIME_LIMIT, connectionString: MIGRATION_URL });
371
+ await recordMigration(client, {
372
+ file: filename,
373
+ table: '_entity_migrations',
374
+ row: { entity_name: entityName, source_type: sourceType, source_name: sourceName, filename },
375
+ limit: TIME_LIMIT,
376
+ });
365
377
 
366
378
  console.log(`${indent}✅ ${filename} executed successfully`);
367
379
  return true;
@@ -369,14 +381,7 @@ async function executeEntityMigration(client, migration) {
369
381
 
370
382
  // Entity migrations runner - WordPress-like architecture with sample_data deferred execution
371
383
  async function runEntityMigrations() {
372
- const client = new Client({
373
- connectionString: MIGRATION_URL,
374
- ssl: {
375
- rejectUnauthorized: false,
376
- require: true
377
- },
378
- connectionTimeoutMillis: 10000
379
- });
384
+ const client = migrationClient(MIGRATION_URL, TIME_LIMIT);
380
385
 
381
386
  try {
382
387
  await client.connect();
@@ -545,7 +550,7 @@ async function runEntityMigrations() {
545
550
  const executed = await executeContentMigration(client, migration);
546
551
  if (executed) totalContentMigrations++;
547
552
  } catch (error) {
548
- console.error(` ❌ Failed to execute ${migration.filename}:`, error.message);
553
+ console.error(` ❌ ${migrationFailure(migration.filename, error)}`);
549
554
  throw error;
550
555
  }
551
556
  }
@@ -566,7 +571,7 @@ async function runEntityMigrations() {
566
571
  const executed = await executeEntityMigration(client, migration);
567
572
  if (executed) totalEntityMigrations++;
568
573
  } catch (error) {
569
- console.error(` ❌ Failed to execute ${migration.filename}:`, error.message);
574
+ console.error(` ❌ ${migrationFailure(migration.filename, error)}`);
570
575
  throw error;
571
576
  }
572
577
  }
@@ -586,7 +591,7 @@ async function runEntityMigrations() {
586
591
  const executed = await executeContentMigration(client, migration);
587
592
  if (executed) totalContentMigrations++;
588
593
  } catch (error) {
589
- console.error(` ❌ Failed to execute ${migration.filename}:`, error.message);
594
+ console.error(` ❌ ${migrationFailure(migration.filename, error)}`);
590
595
  throw error;
591
596
  }
592
597
  }
@@ -607,7 +612,7 @@ async function runEntityMigrations() {
607
612
  const executed = await executeEntityMigration(client, migration);
608
613
  if (executed) totalEntityMigrations++;
609
614
  } catch (error) {
610
- console.error(` ❌ Failed to execute ${migration.filename}:`, error.message);
615
+ console.error(` ❌ ${migrationFailure(migration.filename, error)}`);
611
616
  throw error;
612
617
  }
613
618
  }
@@ -0,0 +1,194 @@
1
+ import pg from 'pg'
2
+ import { EventEmitter } from 'node:events'
3
+
4
+ /**
5
+ * SSL handling for the database maintenance scripts.
6
+ *
7
+ * This deliberately mirrors `src/lib/db.ts` without importing application
8
+ * runtime code: scripts run as standalone Node ESM programs while the runtime
9
+ * remains bundled separately until its next release.
10
+ */
11
+
12
+ /**
13
+ * Strip SSL-related parameters so pg cannot override the explicit `ssl` option.
14
+ * pg v8+ otherwise resolves `sslmode=require` as certificate validation.
15
+ */
16
+ export function stripSSLParams(databaseUrl) {
17
+ if (!databaseUrl) return databaseUrl
18
+ try {
19
+ const url = new URL(databaseUrl)
20
+ url.searchParams.delete('sslmode')
21
+ url.searchParams.delete('uselibpqcompat')
22
+ return url.toString()
23
+ } catch {
24
+ return databaseUrl
25
+ .replace(/[?&]sslmode=[^&]*/g, '')
26
+ .replace(/[?&]uselibpqcompat=[^&]*/g, '')
27
+ .replace(/\?&/, '?')
28
+ .replace(/\?$/, '')
29
+ }
30
+ }
31
+
32
+ /**
33
+ * Parse the SSL policy used by database scripts.
34
+ *
35
+ * Explicit sslmode takes precedence. With no recognized mode, only production
36
+ * uses validated TLS; all other environments use no TLS.
37
+ */
38
+ export function parseSSLConfig(databaseUrl) {
39
+ const isProduction = process.env.NODE_ENV === 'production'
40
+
41
+ if (!databaseUrl) return isProduction ? { rejectUnauthorized: true } : false
42
+
43
+ try {
44
+ const url = new URL(databaseUrl)
45
+ const sslmode = url.searchParams.get('sslmode')?.trim().toLowerCase()
46
+
47
+ if (sslmode) {
48
+ switch (sslmode) {
49
+ case 'disable':
50
+ if (isProduction) console.warn('[DB] WARNING: SSL disabled in production environment. This is insecure!')
51
+ return false
52
+ case 'require':
53
+ case 'prefer':
54
+ case 'allow':
55
+ return { rejectUnauthorized: false }
56
+ case 'verify-ca':
57
+ case 'verify-full':
58
+ return { rejectUnauthorized: true }
59
+ default:
60
+ console.warn(`[DB] Unknown sslmode value: "${sslmode}". Using environment defaults.`)
61
+ }
62
+ }
63
+
64
+ return isProduction ? { rejectUnauthorized: true } : false
65
+ } catch {
66
+ if (databaseUrl.includes('sslmode=disable')) return false
67
+ return isProduction ? { rejectUnauthorized: true } : false
68
+ }
69
+ }
70
+
71
+ /** True only for a syntactically valid URL which does not state an sslmode. */
72
+ export function prefersSSL(databaseUrl) {
73
+ if (!databaseUrl) return false
74
+ try {
75
+ return !new URL(databaseUrl).searchParams.has('sslmode')
76
+ } catch {
77
+ // Keep malformed URLs on the runtime's conservative, documented policy.
78
+ return false
79
+ }
80
+ }
81
+
82
+ /**
83
+ * The initial configuration for a script connection.
84
+ *
85
+ * A URL without sslmode has libpq's `prefer` semantics: first ask for TLS
86
+ * without validating the certificate, and fall back only when Postgres says it
87
+ * has no TLS support. `parseSSLConfig` intentionally remains the runtime's
88
+ * policy; the distinct initial value here is what makes the scripts differ on
89
+ * that one, documented case.
90
+ */
91
+ export function scriptConnectionOptions(databaseUrl, options = {}) {
92
+ return {
93
+ ...options,
94
+ connectionString: stripSSLParams(databaseUrl),
95
+ ssl: prefersSSL(databaseUrl) ? { rejectUnauthorized: false } : parseSSLConfig(databaseUrl),
96
+ }
97
+ }
98
+
99
+ const NO_SSL_SUPPORT = 'The server does not support SSL connections'
100
+
101
+ function isNoSSLSupport(error) {
102
+ return error?.message === NO_SSL_SUPPORT
103
+ }
104
+
105
+ /**
106
+ * A small Client facade for the only case where scripts deliberately differ
107
+ * from the runtime: a URL with no sslmode. It preserves pg's Client surface
108
+ * while swapping the first TLS-only connection for one without TLS only after
109
+ * pg's precise "server does not support SSL" error. Every other failure is
110
+ * returned unchanged.
111
+ */
112
+ class SSLPreferClient extends EventEmitter {
113
+ constructor(options) {
114
+ super()
115
+ this.options = options
116
+ this.client = new pg.Client(options)
117
+ this.eventsForwarded = false
118
+ }
119
+
120
+ get connection() { return this.client.connection }
121
+ get connectionParameters() { return this.client.connectionParameters }
122
+ get database() { return this.client.database }
123
+ set database(value) { this.client.database = value }
124
+ get processID() { return this.client.processID }
125
+ get _queryable() { return this.client._queryable }
126
+ get _ending() { return this.client._ending }
127
+
128
+ forwardEvents() {
129
+ if (this.eventsForwarded) return
130
+ this.eventsForwarded = true
131
+ // Pool relies on these two; the scripts use the underlying connection for
132
+ // protocol events. Forwarding also keeps the normal pg error/end contract.
133
+ for (const event of ['error', 'end', 'notice', 'notification']) {
134
+ this.client.on(event, (...args) => this.emit(event, ...args))
135
+ }
136
+ }
137
+
138
+ async connectPromise() {
139
+ const startedAt = performance.now()
140
+ try {
141
+ await this.client.connect()
142
+ } catch (error) {
143
+ if (!isNoSSLSupport(error)) throw error
144
+
145
+ // A retry shares, rather than resets, the connection time budget. This
146
+ // matters for timeLimitedClient(), whose connectMs is a hard bound.
147
+ const timeout = Number(this.options.connectionTimeoutMillis) || 0
148
+ const elapsed = performance.now() - startedAt
149
+ if (timeout > 0 && elapsed >= timeout) throw new Error('timeout expired')
150
+
151
+ // The facade exposes pg's live connection parameters. Callers such as
152
+ // timeLimitedClient() deliberately update those after construction so a
153
+ // connection string cannot disable their limits. Build the plaintext
154
+ // retry from that live, resolved state (rather than re-parsing the URL),
155
+ // or it would silently discard every such update.
156
+ const { connectionString: _connectionString, ...options } = this.options
157
+ this.client = new pg.Client({
158
+ ...options,
159
+ ...this.client.connectionParameters,
160
+ password: this.client.password,
161
+ ssl: false,
162
+ ...(timeout > 0 ? { connectionTimeoutMillis: Math.max(1, Math.ceil(timeout - elapsed)) } : {}),
163
+ })
164
+ await this.client.connect()
165
+ }
166
+ this.forwardEvents()
167
+ }
168
+
169
+ connect(callback) {
170
+ const promise = this.connectPromise()
171
+ if (!callback) return promise
172
+ promise.then(() => callback(null, this), callback)
173
+ }
174
+
175
+ query(...args) { return this.client.query(...args) }
176
+ end(...args) { return this.client.end(...args) }
177
+ ref() { return this.client.ref() }
178
+ unref() { return this.client.unref() }
179
+ }
180
+
181
+ /** A Client with explicit modes unchanged, or libpq-style `prefer` otherwise. */
182
+ export function scriptClient(databaseUrl, options = {}) {
183
+ const config = scriptConnectionOptions(databaseUrl, options)
184
+ return prefersSSL(databaseUrl) ? new SSLPreferClient(config) : new pg.Client(config)
185
+ }
186
+
187
+ /** A Pool whose clients have the same `prefer` behavior as direct clients. */
188
+ export function scriptPool(databaseUrl, options = {}) {
189
+ const config = scriptConnectionOptions(databaseUrl, options)
190
+ return new pg.Pool({
191
+ ...config,
192
+ ...(prefersSSL(databaseUrl) ? { Client: SSLPreferClient } : {}),
193
+ })
194
+ }
@@ -0,0 +1,69 @@
1
+ // Where db:verify-theme finds a theme, and the directory run-migrations.mjs has
2
+ // to run in to migrate it.
3
+ //
4
+ // run-migrations.mjs reads the active theme from `<cwd>/contents/themes/<theme>`.
5
+ // The monorepo's themes reach it through apps/dev/contents, which links to
6
+ // themes/. The starter a generated project ships is not among them: it lives in
7
+ // packages/core/templates/contents/themes/starter, and themes/starter holds only
8
+ // its tests. packages/core/templates has the same contents/ layout a generated
9
+ // project has, so the runner reads the starter from there.
10
+
11
+ import fs from 'fs';
12
+ import path from 'path';
13
+
14
+ /** The directories the runner can run in, each with its own contents/themes. */
15
+ export const THEME_PROJECTS = ['apps/dev', 'packages/core/templates'];
16
+
17
+ // The runner reads a theme's plugins from one of these; a directory with
18
+ // neither is not a theme, and migrating it migrates core alone.
19
+ const THEME_CONFIGS = ['config/theme.config.ts', 'theme.config.ts'];
20
+
21
+ const SKIP_DIRS = new Set(['node_modules', '.next', 'dist']);
22
+
23
+ /** Every .sql file inside a `migrations` directory under `dir`, at any depth. */
24
+ export function migrationFiles(dir, insideMigrations = false, found = []) {
25
+ if (!fs.existsSync(dir)) return found;
26
+ for (const entry of fs.readdirSync(dir, { withFileTypes: true })) {
27
+ if (SKIP_DIRS.has(entry.name)) continue;
28
+ const entryPath = path.join(dir, entry.name);
29
+ if (entry.isDirectory()) {
30
+ migrationFiles(entryPath, insideMigrations || entry.name === 'migrations', found);
31
+ } else if (insideMigrations && entry.name.endsWith('.sql')) {
32
+ found.push(entryPath);
33
+ }
34
+ }
35
+ return found;
36
+ }
37
+
38
+ /**
39
+ * The theme called `theme`: its directory, the directory to run the runner in,
40
+ * and the migration files it holds. `{ error }` when there is no such theme,
41
+ * when the name is a theme in more than one place, or when it has no
42
+ * migrations, since a run over none of them proves nothing about it.
43
+ */
44
+ export function findTheme(repoRoot, theme) {
45
+ const candidates = THEME_PROJECTS.map(project => ({
46
+ projectDir: path.join(repoRoot, project),
47
+ themeDir: path.join(repoRoot, project, 'contents', 'themes', theme),
48
+ }));
49
+ // A theme reached through a link is shown where it really lives: themes/blog, not apps/dev/contents/themes/blog.
50
+ const shown = dir =>
51
+ fs.existsSync(dir) ? path.relative(fs.realpathSync(repoRoot), fs.realpathSync(dir)) : path.relative(repoRoot, dir);
52
+ const found = candidates.filter(({ themeDir }) => THEME_CONFIGS.some(config => fs.existsSync(path.join(themeDir, config))));
53
+
54
+ if (found.length === 0) {
55
+ return {
56
+ error: `no theme "${theme}": none of ${candidates.map(({ themeDir }) => shown(themeDir)).join(', ')} has a theme config`,
57
+ };
58
+ }
59
+ if (found.length > 1) {
60
+ return { error: `"${theme}" is a theme in more than one place: ${found.map(({ themeDir }) => shown(themeDir)).join(', ')}` };
61
+ }
62
+
63
+ const [{ projectDir, themeDir }] = found;
64
+ const migrations = migrationFiles(themeDir);
65
+ if (migrations.length === 0) {
66
+ return { error: `theme "${theme}" at ${shown(themeDir)} has no migrations to verify` };
67
+ }
68
+ return { projectDir, themeDir, shownDir: shown(themeDir), migrations };
69
+ }
@@ -1,9 +1,7 @@
1
- import pg from "pg";
2
1
  import fs from "fs";
3
2
  import path from "path";
4
3
  import { fileURLToPath } from 'url';
5
-
6
- const { Client } = pg;
4
+ import { scriptClient } from './ssl-config.mjs';
7
5
 
8
6
  const __filename = fileURLToPath(import.meta.url);
9
7
  const __dirname = path.dirname(__filename);
@@ -24,10 +22,7 @@ envLines.forEach(line => {
24
22
  });
25
23
 
26
24
  async function verifyTables() {
27
- const client = new Client({
28
- connectionString: DATABASE_URL,
29
- ssl: { rejectUnauthorized: false }
30
- });
25
+ const client = scriptClient(DATABASE_URL);
31
26
 
32
27
  try {
33
28
  await client.connect();
@@ -139,4 +134,4 @@ async function verifyTables() {
139
134
  }
140
135
  }
141
136
 
142
- verifyTables();
137
+ verifyTables();
@@ -5,17 +5,13 @@
5
5
  * Run with: node core/scripts/db/verify-teams.mjs
6
6
  */
7
7
 
8
- import pg from 'pg'
9
8
  import dotenv from 'dotenv'
9
+ import { scriptPool } from './ssl-config.mjs'
10
10
 
11
11
  dotenv.config()
12
12
 
13
- const { Pool } = pg
14
-
15
13
  async function verifyTeamsSetup() {
16
- const pool = new Pool({
17
- connectionString: process.env.DATABASE_URL,
18
- })
14
+ const pool = scriptPool(process.env.DATABASE_URL)
19
15
 
20
16
  try {
21
17
  console.log('🔍 Verifying Teams Core Setup...\n')
@@ -0,0 +1,169 @@
1
+ // Runs a single theme's migrations end to end against an explicitly-named
2
+ // database, to catch problems like #193 (sample data referencing a row no
3
+ // migration creates) before they ship. Refuses to run against a database that
4
+ // already holds anything, since running a theme's migrations there would mix
5
+ // that theme's schema and sample data into whatever is already there.
6
+ //
7
+ // Usage: VERIFY_THEME_DATABASE_URL=<database-url> pnpm db:verify-theme <theme>
8
+ //
9
+ // The theme is looked up among the monorepo's themes and the starter core
10
+ // ships to generated projects (see theme-location.mjs). A name that is not a
11
+ // theme, or a theme with no migrations, is refused: a run over core's
12
+ // migrations alone would pass without saying anything about the theme.
13
+ //
14
+ // The run reaches past that database: the core migrations create and alter
15
+ // cluster-wide roles (see cluster-changes.mjs), which every database on the
16
+ // same Postgres server shares. The output names those roles before the
17
+ // migrations run, and the run is refused when the server shows signs of being
18
+ // in use — one of those roles already exists, another database lives there, or
19
+ // the server will not say, or not in time, what is on it.
20
+ // VERIFY_THEME_ALLOW_CLUSTER_CHANGES=1 says the server is yours to change and
21
+ // runs anyway. A disposable server, one per run, needs neither.
22
+ //
23
+ // The database URL comes from the environment, never from an argument:
24
+ // arguments show up in pnpm's command echo and in the process list, and the
25
+ // URL carries the password. The output names only the host and the database.
26
+ //
27
+ // The migrations run through run-migrations.mjs, the same script
28
+ // `pnpm db:migrate` uses, with `--no-env-file` and the theme and database in
29
+ // its environment. No file is read or written for that: no .env is touched,
30
+ // even if this process is killed mid-run.
31
+ //
32
+ // MIGRATE_DATABASE_URL is removed from the child's environment because
33
+ // run-migrations.mjs connects to it in preference to DATABASE_URL, which
34
+ // would migrate a database this script never checked.
35
+ //
36
+ // Every migration runs under a time limit, MIGRATION_TIMEOUT_SECONDS (30 s
37
+ // unless set; see migration-time-limit.mjs). A migration that runs past it
38
+ // fails the run with the migration's name, and its session on the server is
39
+ // ended, instead of leaving the command waiting.
40
+
41
+ import path from 'path';
42
+ import { fileURLToPath } from 'url';
43
+ import { spawnSync } from 'child_process';
44
+ import { GLOBAL_OBJECTS, inspectCluster } from './cluster-changes.mjs';
45
+ import { inspectTarget, inspectMaintenanceDatabase } from './inspect-server.mjs';
46
+ import { findTheme } from './theme-location.mjs';
47
+ import { TIME_LIMIT_VARIABLE, migrationTimeLimit } from './migration-time-limit.mjs';
48
+
49
+ const __filename = fileURLToPath(import.meta.url);
50
+ const __dirname = path.dirname(__filename);
51
+ const repoRoot = path.join(__dirname, '..', '..', '..', '..'); // scripts/db/ -> core/ -> packages/ -> repo root
52
+ const runnerPath = path.join(repoRoot, 'packages', 'core', 'scripts', 'db', 'run-migrations.mjs');
53
+
54
+ const USAGE = 'Usage: VERIFY_THEME_DATABASE_URL=<database-url> pnpm db:verify-theme <theme>';
55
+
56
+ const ACKNOWLEDGEMENT_VAR = 'VERIFY_THEME_ALLOW_CLUSTER_CHANGES';
57
+ const acknowledgesClusterChanges = process.env[ACKNOWLEDGEMENT_VAR] === '1';
58
+
59
+ const args = process.argv.slice(2);
60
+ const theme = args[0];
61
+ const databaseUrl = process.env.VERIFY_THEME_DATABASE_URL;
62
+
63
+ // An extra argument, or a theme that looks like a URL, is most likely the
64
+ // database URL passed the old way: reject it without printing it.
65
+ if (!theme || args.length > 1 || theme.includes('://')) {
66
+ if (theme) console.error('❌ Pass the database URL in VERIFY_THEME_DATABASE_URL, not as an argument.');
67
+ console.error(USAGE);
68
+ process.exit(1);
69
+ }
70
+
71
+ if (!databaseUrl) {
72
+ console.error(`❌ VERIFY_THEME_DATABASE_URL is not set.\n${USAGE}`);
73
+ process.exit(1);
74
+ }
75
+
76
+ let target;
77
+ try {
78
+ const url = new URL(databaseUrl);
79
+ target = `${url.host}${url.pathname}`;
80
+ } catch {
81
+ console.error('❌ VERIFY_THEME_DATABASE_URL is not a valid URL.');
82
+ process.exit(1);
83
+ }
84
+
85
+ const located = findTheme(repoRoot, theme);
86
+ if (located.error) {
87
+ console.error(`❌ ${located.error}`);
88
+ process.exit(1);
89
+ }
90
+
91
+ // Against an empty database no migration a theme ships takes a second; the
92
+ // default leaves a slow runner room many times over, and a migration that needs
93
+ // more can be given it.
94
+ const DEFAULT_TIME_LIMIT_SECONDS = '30';
95
+ const timeLimitSetting = process.env[TIME_LIMIT_VARIABLE] || DEFAULT_TIME_LIMIT_SECONDS;
96
+ let timeLimit;
97
+ try {
98
+ timeLimit = migrationTimeLimit({ [TIME_LIMIT_VARIABLE]: timeLimitSetting });
99
+ } catch (error) {
100
+ console.error(`❌ ${error.message}`);
101
+ process.exit(1);
102
+ }
103
+
104
+ async function main() {
105
+ console.log(`🔎 Verifying theme "${theme}" (${located.shownDir}, ${located.migrations.length} migration files) against ${target}...\n`);
106
+
107
+ let inspection;
108
+ try {
109
+ inspection = await inspectTarget(databaseUrl);
110
+ } catch (error) {
111
+ console.error(`❌ Could not inspect ${target}: ${error.message}`);
112
+ process.exit(1);
113
+ }
114
+
115
+ if (inspection.existingObjects.length > 0) {
116
+ const examples = inspection.existingObjects.map(({ kind, name }) => `${name} (${kind})`).join(', ');
117
+ console.error(
118
+ `❌ Refusing to run: ${target} already holds objects, for example ${examples}.\n` +
119
+ ' This script only runs against a fresh, empty database, to avoid mixing\n' +
120
+ ' this theme\'s schema and sample data into whatever is already there.'
121
+ );
122
+ process.exit(1);
123
+ }
124
+
125
+ console.log('This run also changes these cluster-wide roles, which every database on this server shares:');
126
+ for (const { role, change } of GLOBAL_OBJECTS) console.log(` ${role}: ${change}`);
127
+ console.log('');
128
+
129
+ const maintenance = await inspectMaintenanceDatabase(databaseUrl);
130
+ const cluster = inspectCluster({ ...inspection, maintenance });
131
+ if (!cluster.disposable && !acknowledgesClusterChanges) {
132
+ console.error(
133
+ `❌ Refusing to run: ${target} is on a Postgres server that is not this run's to change.\n` +
134
+ cluster.reasons.map(reason => ` - ${reason}\n`).join('') +
135
+ ' The roles above are cluster-wide: altering them here reaches every database\n' +
136
+ ' on this server. Point the run at a disposable Postgres server, or, if this\n' +
137
+ ` server is yours to change, re-run it with ${ACKNOWLEDGEMENT_VAR}=1.`
138
+ );
139
+ process.exit(1);
140
+ }
141
+ if (!cluster.disposable) {
142
+ console.log(`⚠️ ${ACKNOWLEDGEMENT_VAR}=1: changing those roles on a server in use (${cluster.reasons.join('; ')}).\n`);
143
+ }
144
+
145
+ console.log(`Each migration may run for ${timeLimit.seconds} s; ${TIME_LIMIT_VARIABLE} changes that.\n`);
146
+
147
+ const { MIGRATE_DATABASE_URL: _migrateUrl, VERIFY_THEME_DATABASE_URL: _verifyUrl, ...inheritedEnv } = process.env;
148
+
149
+ const result = spawnSync(process.execPath, [runnerPath, '--no-env-file'], {
150
+ cwd: located.projectDir,
151
+ stdio: 'inherit',
152
+ env: {
153
+ ...inheritedEnv,
154
+ DATABASE_URL: databaseUrl,
155
+ NEXT_PUBLIC_ACTIVE_THEME: theme,
156
+ [TIME_LIMIT_VARIABLE]: timeLimitSetting,
157
+ },
158
+ });
159
+
160
+ if (result.status !== 0) {
161
+ const reason = result.signal ? `signal ${result.signal}` : `exit code ${result.status}`;
162
+ console.error(`\n❌ Theme "${theme}" migrations failed (${reason}).`);
163
+ process.exit(result.status ?? 1);
164
+ }
165
+
166
+ console.log(`\n✅ Theme "${theme}" migrations ran cleanly against ${target}.`);
167
+ }
168
+
169
+ main();