@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
@@ -0,0 +1,367 @@
1
+ /**
2
+ * Tests for how the registry build keeps app/(templates): the tree holds exactly
3
+ * what the project's app layouts and theme templates produce now, and anything
4
+ * the build replaces with different content or removes is backed up first.
5
+ *
6
+ * Run: node --test packages/core/scripts/build/registry/__tests__/templates-tree.test.mjs
7
+ */
8
+
9
+ import { test } from 'node:test'
10
+ import assert from 'node:assert/strict'
11
+ import { chmod, mkdtemp, mkdir, writeFile, readFile, readdir, stat, rm, symlink } from 'node:fs/promises'
12
+ import { existsSync } from 'node:fs'
13
+ import { execFileSync } from 'node:child_process'
14
+ import { tmpdir } from 'node:os'
15
+ import { join, dirname, relative, sep } from 'node:path'
16
+
17
+ import { generateMissingPages, planMissingPages } from '../post-build/page-generator.mjs'
18
+ import { guardConsole } from '../../../utils/logging.mjs'
19
+
20
+ const DASHBOARD_LAYOUT = "'use client'\n\nexport default function DashboardLayout({ children }) { return children }\n"
21
+ const PAGE = 'export default function Page() { return null }\n'
22
+
23
+ async function createProject() {
24
+ return mkdtemp(join(tmpdir(), 'nextspark-templates-tree-test-'))
25
+ }
26
+
27
+ async function writeProjectFile(root, relativePath, content) {
28
+ const absolutePath = join(root, relativePath)
29
+ await mkdir(dirname(absolutePath), { recursive: true })
30
+ await writeFile(absolutePath, content, 'utf8')
31
+ }
32
+
33
+ /** A theme template at `contents/themes/testtheme/templates/<relativePath>`, overriding `app/<relativePath>`. */
34
+ async function writeTemplate(root, relativePath, templateType, content) {
35
+ await writeProjectFile(root, join('contents/themes/testtheme/templates', relativePath), content)
36
+ return {
37
+ name: relativePath.replace(/\.(tsx|ts)$/, ''),
38
+ themeName: 'testtheme',
39
+ templateType,
40
+ fileName: relativePath.split('/').pop(),
41
+ relativePath,
42
+ appPath: `app/${relativePath}`,
43
+ templatePath: `@/contents/themes/testtheme/templates/${relativePath}`,
44
+ priority: 10
45
+ }
46
+ }
47
+
48
+ /** Every file under .nextspark/backups, as its path inside the backup plus its content. */
49
+ async function backedUpFiles(root) {
50
+ const backupsDir = join(root, '.nextspark/backups')
51
+ if (!existsSync(backupsDir)) return []
52
+
53
+ const files = []
54
+ for (const run of await readdir(backupsDir, { withFileTypes: true })) {
55
+ if (!run.isDirectory()) continue
56
+ const runDir = join(backupsDir, run.name)
57
+ const walk = async directory => {
58
+ for (const entry of await readdir(directory, { withFileTypes: true })) {
59
+ const path = join(directory, entry.name)
60
+ if (entry.isDirectory()) {
61
+ await walk(path)
62
+ } else {
63
+ files.push({ path: relative(runDir, path).split(sep).join('/'), content: await readFile(path, 'utf8') })
64
+ }
65
+ }
66
+ }
67
+ await walk(runDir)
68
+ }
69
+ return files
70
+ }
71
+
72
+ test('a stale copy of an app layout is refreshed, and what it held is backed up', async () => {
73
+ const root = await createProject()
74
+ try {
75
+ await writeProjectFile(root, 'app/dashboard/layout.tsx', DASHBOARD_LAYOUT)
76
+ await writeProjectFile(root, 'app/(templates)/dashboard/layout.tsx', "'use client'\n// copied by an older release\n")
77
+ const page = await writeTemplate(root, 'dashboard/reports/page.tsx', 'page', PAGE)
78
+
79
+ await generateMissingPages([page], { projectRoot: root })
80
+
81
+ const copy = await readFile(join(root, 'app/(templates)/dashboard/layout.tsx'), 'utf8')
82
+ assert.match(copy, /^\/\*\*\n \* Generated by the NextSpark registry build: a copy of app\/dashboard\/layout\.tsx/)
83
+ assert.ok(copy.endsWith(DASHBOARD_LAYOUT), 'the copy is the app layout, directive included, after the generated comment')
84
+
85
+ assert.deepEqual(await backedUpFiles(root), [
86
+ { path: 'app/(templates)/dashboard/layout.tsx', content: "'use client'\n// copied by an older release\n" }
87
+ ])
88
+ } finally {
89
+ await rm(root, { recursive: true, force: true })
90
+ }
91
+ })
92
+
93
+ test('a layout copy no generated route needs any more is removed, and backed up', async () => {
94
+ const root = await createProject()
95
+ try {
96
+ await writeProjectFile(root, 'app/(templates)/old/layout.tsx', DASHBOARD_LAYOUT)
97
+ const page = await writeTemplate(root, 'pricing/page.tsx', 'page', PAGE)
98
+
99
+ await generateMissingPages([page], { projectRoot: root })
100
+
101
+ assert.equal(existsSync(join(root, 'app/(templates)/old/layout.tsx')), false)
102
+ assert.equal(existsSync(join(root, 'app/(templates)/old')), false)
103
+ assert.deepEqual(await backedUpFiles(root), [{ path: 'app/(templates)/old/layout.tsx', content: DASHBOARD_LAYOUT }])
104
+ } finally {
105
+ await rm(root, { recursive: true, force: true })
106
+ }
107
+ })
108
+
109
+ test('the route file of a template whose app route now exists is removed, and backed up', async () => {
110
+ const root = await createProject()
111
+ try {
112
+ const page = await writeTemplate(root, 'pricing/page.tsx', 'page', PAGE)
113
+ await generateMissingPages([page], { projectRoot: root })
114
+ const generated = await readFile(join(root, 'app/(templates)/pricing/page.tsx'), 'utf8')
115
+
116
+ await writeProjectFile(root, 'app/pricing/page.tsx', PAGE)
117
+ await generateMissingPages([page], { projectRoot: root })
118
+
119
+ assert.equal(existsSync(join(root, 'app/(templates)/pricing/page.tsx')), false)
120
+ assert.deepEqual(await backedUpFiles(root), [{ path: 'app/(templates)/pricing/page.tsx', content: generated }])
121
+ } finally {
122
+ await rm(root, { recursive: true, force: true })
123
+ }
124
+ })
125
+
126
+ test('regenerating a tree that is already up to date writes nothing and backs nothing up', async () => {
127
+ const root = await createProject()
128
+ try {
129
+ await writeProjectFile(root, 'app/dashboard/layout.tsx', DASHBOARD_LAYOUT)
130
+ const page = await writeTemplate(root, 'dashboard/reports/page.tsx', 'page', PAGE)
131
+ const layout = await writeTemplate(root, 'dashboard/reports/layout.tsx', 'layout', "export const metadata = { title: 'Reports' }\n")
132
+
133
+ await generateMissingPages([page, layout], { projectRoot: root })
134
+ const generatedFiles = ['dashboard/layout.tsx', 'dashboard/reports/page.tsx', 'dashboard/reports/layout.tsx']
135
+ .map(file => join(root, 'app/(templates)', file))
136
+ const modifiedTimes = await Promise.all(generatedFiles.map(async file => (await stat(file)).mtimeMs))
137
+
138
+ await generateMissingPages([page, layout], { projectRoot: root })
139
+
140
+ assert.deepEqual(await Promise.all(generatedFiles.map(async file => (await stat(file)).mtimeMs)), modifiedTimes)
141
+ assert.equal(existsSync(join(root, '.nextspark/backups')), false)
142
+ } finally {
143
+ await rm(root, { recursive: true, force: true })
144
+ }
145
+ })
146
+
147
+ test('two builds in the same millisecond keep both backups', async () => {
148
+ const root = await createProject()
149
+ const toISOString = Date.prototype.toISOString
150
+ Date.prototype.toISOString = () => '2026-01-01T00:00:00.000Z'
151
+ try {
152
+ await writeProjectFile(root, 'app/dashboard/layout.tsx', DASHBOARD_LAYOUT)
153
+ const page = await writeTemplate(root, 'dashboard/reports/page.tsx', 'page', PAGE)
154
+
155
+ await writeProjectFile(root, 'app/(templates)/dashboard/layout.tsx', '// first hand edit\n')
156
+ await generateMissingPages([page], { projectRoot: root })
157
+ await writeProjectFile(root, 'app/(templates)/dashboard/layout.tsx', '// second hand edit\n')
158
+ await generateMissingPages([page], { projectRoot: root })
159
+
160
+ assert.deepEqual(
161
+ (await backedUpFiles(root)).map(({ content }) => content).sort(),
162
+ ['// first hand edit\n', '// second hand edit\n']
163
+ )
164
+ } finally {
165
+ Date.prototype.toISOString = toISOString
166
+ await rm(root, { recursive: true, force: true })
167
+ }
168
+ })
169
+
170
+ test('a dotfile left in app/(templates) is removed like any other file, and backed up', async () => {
171
+ const root = await createProject()
172
+ try {
173
+ await writeProjectFile(root, 'app/(templates)/dashboard/.user-note', 'remember to check the layout\n')
174
+ const page = await writeTemplate(root, 'pricing/page.tsx', 'page', PAGE)
175
+
176
+ await generateMissingPages([page], { projectRoot: root })
177
+
178
+ assert.equal(existsSync(join(root, 'app/(templates)/dashboard/.user-note')), false)
179
+ assert.deepEqual(await backedUpFiles(root), [
180
+ { path: 'app/(templates)/dashboard/.user-note', content: 'remember to check the layout\n' }
181
+ ])
182
+ } finally {
183
+ await rm(root, { recursive: true, force: true })
184
+ }
185
+ })
186
+
187
+ /** What git status lists under .nextspark, untracked files one by one. */
188
+ function nextsparkPathsForGit(root) {
189
+ return execFileSync('git', ['status', '--porcelain', '-z', '--untracked-files=all'], { cwd: root, encoding: 'utf8' })
190
+ .split('\0')
191
+ .filter(entry => entry.slice(3).startsWith('.nextspark'))
192
+ }
193
+
194
+ /** Every file and symlink under `dir`, with its content or target, for telling whether anything changed. */
195
+ async function snapshot(dir) {
196
+ const entries = []
197
+ const walk = async directory => {
198
+ for (const entry of (await readdir(directory, { withFileTypes: true })).sort((a, b) => a.name.localeCompare(b.name))) {
199
+ const path = join(directory, entry.name)
200
+ if (entry.isDirectory()) {
201
+ entries.push(`${relative(dir, path)}/`)
202
+ await walk(path)
203
+ } else {
204
+ entries.push(`${relative(dir, path)} ${entry.isFile() ? await readFile(path, 'utf8') : '(not a file)'}`)
205
+ }
206
+ }
207
+ }
208
+ await walk(dir)
209
+ return entries.join('\n')
210
+ }
211
+
212
+ test('a backup the build takes is out of git from the moment it is written, whatever the project\'s .gitignore leaves in', { skip: process.getuid?.() === 0 }, async () => {
213
+ const root = await createProject()
214
+ try {
215
+ execFileSync('git', ['init', '-q'], { cwd: root })
216
+ await writeProjectFile(root, '.gitignore', 'app/(templates)/\n!.nextspark/backups/**\n')
217
+ await writeProjectFile(root, 'app/dashboard/layout.tsx', DASHBOARD_LAYOUT)
218
+ await writeProjectFile(root, 'app/(templates)/dashboard/layout.tsx', '// replaced first\n')
219
+ await writeProjectFile(root, 'app/(templates)/old/page.tsx', '// removed after\n')
220
+ const page = await writeTemplate(root, 'dashboard/reports/page.tsx', 'page', PAGE)
221
+
222
+ // The page it removes can't be read, so the build stops once it has backed up the layout it replaces
223
+ await chmod(join(root, 'app/(templates)/old/page.tsx'), 0o000)
224
+ await assert.rejects(generateMissingPages([page], { projectRoot: root }), /EACCES/)
225
+ assert.deepEqual((await backedUpFiles(root)).map(({ path }) => path), ['app/(templates)/dashboard/layout.tsx'])
226
+ assert.deepEqual(nextsparkPathsForGit(root), [], 'git picks up nothing while the build is partway')
227
+
228
+ await chmod(join(root, 'app/(templates)/old/page.tsx'), 0o644)
229
+ await generateMissingPages([page], { projectRoot: root })
230
+ assert.equal((await backedUpFiles(root)).length, 2)
231
+ assert.deepEqual(nextsparkPathsForGit(root), [], 'git picks up nothing once the build is done')
232
+ assert.equal(
233
+ await readFile(join(root, '.nextspark/backups/.gitignore'), 'utf8'),
234
+ '# Backups nextspark keeps on this machine: none of them belongs in git\n*\n'
235
+ )
236
+ } finally {
237
+ await rm(root, { recursive: true, force: true })
238
+ }
239
+ })
240
+
241
+ test('a build with something to back up writes nothing in the tree while .nextspark/backups can\'t keep it out of git', async () => {
242
+ const cases = [
243
+ { name: '.nextspark/backups/.gitignore is a symlink', message: /\.nextspark\/backups\/\.gitignore is a symlink, which git does not read/, setUp: async (root, outside) => {
244
+ await writeProjectFile(outside, 'rules', '*\n')
245
+ await mkdir(join(root, '.nextspark/backups'), { recursive: true })
246
+ await symlink(join(outside, 'rules'), join(root, '.nextspark/backups/.gitignore'))
247
+ } },
248
+ { name: '.nextspark/backups/.gitignore takes a backup back', message: /\.nextspark\/backups\/\.gitignore has patterns other than \*, which can take what is beside it back into git/, setUp: async root => {
249
+ await writeProjectFile(root, '.nextspark/backups/.gitignore', '*\n!*/\n')
250
+ } },
251
+ { name: '.nextspark/backups is a symlink', message: /\.nextspark\/backups is a symlink/, setUp: async (root, outside) => {
252
+ await mkdir(join(root, '.nextspark'), { recursive: true })
253
+ await symlink(outside, join(root, '.nextspark/backups'))
254
+ } },
255
+ { name: '.nextspark is a file', message: /\.nextspark is not a directory/, setUp: async root => {
256
+ await writeProjectFile(root, '.nextspark', '')
257
+ } },
258
+ ]
259
+
260
+ // Every case runs before anything is asserted, so a failure names each one that writes
261
+ const wrong = []
262
+ for (const { name, message, setUp } of cases) {
263
+ const root = await createProject()
264
+ const outside = await mkdtemp(join(tmpdir(), 'nextspark-templates-tree-outside-'))
265
+ try {
266
+ await writeProjectFile(root, 'app/(templates)/old/layout.tsx', DASHBOARD_LAYOUT)
267
+ const page = await writeTemplate(root, 'pricing/page.tsx', 'page', PAGE)
268
+ await setUp(root, outside)
269
+ const before = { project: await snapshot(root), outside: await snapshot(outside) }
270
+
271
+ try {
272
+ await generateMissingPages([page], { projectRoot: root })
273
+ wrong.push(`${name}: the build went on`)
274
+ } catch (error) {
275
+ if (!message.test(error.message)) wrong.push(`${name}: ${error.message}`)
276
+ }
277
+ if ((await snapshot(root)) !== before.project) wrong.push(`${name}: wrote in the project`)
278
+ if ((await snapshot(outside)) !== before.outside) wrong.push(`${name}: wrote outside the project`)
279
+ } finally {
280
+ await rm(outside, { recursive: true, force: true })
281
+ await rm(root, { recursive: true, force: true })
282
+ }
283
+ }
284
+
285
+ assert.deepEqual(wrong, [])
286
+ })
287
+
288
+ test('a .gitignore already in .nextspark/backups with * as its only pattern is kept as it is', async () => {
289
+ const root = await createProject()
290
+ try {
291
+ await writeProjectFile(root, '.nextspark/backups/.gitignore', '# ours\r\n* \r\n')
292
+ await writeProjectFile(root, 'app/(templates)/old/layout.tsx', DASHBOARD_LAYOUT)
293
+ const page = await writeTemplate(root, 'pricing/page.tsx', 'page', PAGE)
294
+
295
+ await generateMissingPages([page], { projectRoot: root })
296
+
297
+ assert.equal(await readFile(join(root, '.nextspark/backups/.gitignore'), 'utf8'), '# ours\r\n* \r\n')
298
+ assert.deepEqual(await backedUpFiles(root), [{ path: 'app/(templates)/old/layout.tsx', content: DASHBOARD_LAYOUT }])
299
+ } finally {
300
+ await rm(root, { recursive: true, force: true })
301
+ }
302
+ })
303
+
304
+ test('a file where the tree needs a directory, or a directory where it needs a file, is planned, backed up and removed before the build writes there', async () => {
305
+ const root = await createProject()
306
+ try {
307
+ await writeProjectFile(root, 'app/dashboard/layout.tsx', DASHBOARD_LAYOUT)
308
+ await writeProjectFile(root, 'app/(templates)/dashboard', '// a file where a directory goes\n')
309
+ await writeProjectFile(root, 'app/(templates)/pricing/page.tsx/note.md', 'a directory where a file goes\n')
310
+ await writeProjectFile(root, 'app/(templates)/pricing/page.tsx/.DS_Store', '')
311
+ await mkdir(join(root, 'app/(templates)/pricing/page.tsx/empty'), { recursive: true })
312
+ const reports = await writeTemplate(root, 'dashboard/reports/page.tsx', 'page', PAGE)
313
+ const pricing = await writeTemplate(root, 'pricing/page.tsx', 'page', PAGE)
314
+
315
+ assert.deepEqual(await planMissingPages([reports, pricing], { projectRoot: root }), {
316
+ create: ['app/(templates)/dashboard/layout.tsx', 'app/(templates)/dashboard/reports/page.tsx', 'app/(templates)/pricing/page.tsx'],
317
+ replace: [],
318
+ remove: ['app/(templates)/dashboard', 'app/(templates)/pricing/page.tsx/note.md'],
319
+ })
320
+
321
+ await generateMissingPages([reports, pricing], { projectRoot: root })
322
+
323
+ for (const file of ['dashboard/layout.tsx', 'dashboard/reports/page.tsx', 'pricing/page.tsx']) {
324
+ assert.ok((await stat(join(root, 'app/(templates)', file))).isFile(), `${file} is written`)
325
+ }
326
+ assert.deepEqual((await backedUpFiles(root)).sort((a, b) => a.path.localeCompare(b.path)), [
327
+ { path: 'app/(templates)/dashboard', content: '// a file where a directory goes\n' },
328
+ { path: 'app/(templates)/pricing/page.tsx/note.md', content: 'a directory where a file goes\n' },
329
+ ])
330
+ } finally {
331
+ await rm(root, { recursive: true, force: true })
332
+ }
333
+ })
334
+
335
+ test('a line the build prints that names a path whose name would break or reorder it is escaped, once the console is guarded as the build guards it', async () => {
336
+ const names = [
337
+ ['forged\n✅ Registry System built successfully!.tsx', 'forged\\n✅ Registry System built successfully!.tsx'],
338
+ ['erase\u001b[2Kline.tsx', 'erase\\u001b[2Kline.tsx'],
339
+ ['carriage\rreturn.tsx', 'carriage\\rreturn.tsx'],
340
+ ['line\u2028separator.tsx', 'line\\u2028separator.tsx'],
341
+ ['next\u0085line.tsx', 'next\\u0085line.tsx'],
342
+ ]
343
+ const root = await createProject()
344
+ const printed = []
345
+ const originalLog = console.log
346
+ try {
347
+ for (const [name] of names) await writeProjectFile(root, `app/(templates)/old/${name}`, '// mine\n')
348
+ const page = await writeTemplate(root, 'pricing/page.tsx', 'page', PAGE)
349
+
350
+ console.log = (...args) => { printed.push(args.join(' ')) }
351
+ guardConsole()
352
+ try {
353
+ await generateMissingPages([page], { projectRoot: root })
354
+ } finally {
355
+ console.log = originalLog
356
+ }
357
+
358
+ const lines = printed.join('\n').split('\n')
359
+ assert.deepEqual(lines.filter(line => /[\u0000-\u0009\u000b-\u001f\u007f-\u009f\u061c\u200e\u200f\u2028\u2029\u202a-\u202e\u2066-\u2069]/.test(line)), [], 'no line holds a raw control')
360
+ assert.ok(!lines.some(line => line.startsWith('✅ Registry System')), 'no line reads as the build succeeding')
361
+ for (const [, shown] of names) {
362
+ assert.ok(lines.some(line => line.startsWith(`"⚠️ app/(templates): backed up app/(templates)/old/${shown} to .nextspark/backups/`)), `${shown} is named escaped`)
363
+ }
364
+ } finally {
365
+ await rm(root, { recursive: true, force: true })
366
+ }
367
+ })