@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,279 @@
1
+ /**
2
+ * The one way the generator writes, creates directories, renames and removes:
3
+ * the registry build and the rest of scripts/build, and `nextspark sync:app`,
4
+ * which loads this module from the core installed in the project. No other file
5
+ * of the generator calls fs to change anything; a test fails when one does.
6
+ *
7
+ * Each call is bound to a root, the directory it may change things under: the
8
+ * project, or, for the themes of the monorepo, the monorepo. Before it touches
9
+ * anything the call looks at the path it was given, from the root down, and
10
+ * refuses - throwing an error with the code UNSAFE_WRITE and writing nothing -
11
+ * when the path is outside the root, when something along it inside the root is
12
+ * a symlink, the path itself included, when a part of it can't be looked at,
13
+ * when what exists of it resolves outside the root, or when the file it would
14
+ * write into has other hard links, which writing it would change too. What goes
15
+ * through a symlink lands wherever the symlink points; what a hard link shares
16
+ * is written wherever the other names are.
17
+ *
18
+ * What the check looks at is what is on disk when the call runs. A file is
19
+ * opened without following a symlink put in its place after the check; a
20
+ * directory along the path replaced by one in between is not caught.
21
+ *
22
+ * @module core/scripts/build/safe-fs
23
+ */
24
+
25
+ import {
26
+ closeSync,
27
+ constants,
28
+ fchmodSync,
29
+ fstatSync,
30
+ lstatSync,
31
+ mkdirSync,
32
+ mkdtempSync,
33
+ openSync,
34
+ readSync,
35
+ realpathSync,
36
+ renameSync,
37
+ rmdirSync,
38
+ rmSync,
39
+ unlinkSync,
40
+ writeFileSync,
41
+ writeSync,
42
+ } from 'fs'
43
+ import { dirname, isAbsolute, join, relative, resolve, sep } from 'path'
44
+
45
+ /** The code of the error a refused call throws. */
46
+ export const UNSAFE_WRITE = 'UNSAFE_WRITE'
47
+
48
+ const NO_FOLLOW = constants.O_NOFOLLOW ?? 0
49
+
50
+ /** The open flags of the writeFile flags the generator uses, none of them following a symlink. */
51
+ const WRITE_FLAGS = {
52
+ w: constants.O_WRONLY | constants.O_CREAT | constants.O_TRUNC | NO_FOLLOW,
53
+ wx: constants.O_WRONLY | constants.O_CREAT | constants.O_EXCL | NO_FOLLOW,
54
+ }
55
+
56
+ /** The size of the buffer copyFile reads and writes through: memory use stays at this regardless of file size. */
57
+ export const COPY_BLOCK_SIZE = 1024 * 1024
58
+
59
+ /** The copyFile mode bits safe-fs can honor without following the destination: no flag, or COPYFILE_EXCL. */
60
+ const KNOWN_COPY_MODE = constants.COPYFILE_EXCL
61
+
62
+ /**
63
+ * Whether `error` is a refusal of this module: nothing was written, and the
64
+ * caller must not carry on as if it had been, or read it as a missing file.
65
+ *
66
+ * @param {unknown} error
67
+ * @returns {boolean}
68
+ */
69
+ export function isUnsafeWrite(error) {
70
+ return error instanceof Error && error.code === UNSAFE_WRITE
71
+ }
72
+
73
+ /** A path from `base` with forward slashes, as a refusal names it. */
74
+ function fromBase(base, absolute) {
75
+ return relative(base, absolute).split(sep).join('/') || '.'
76
+ }
77
+
78
+ /** `path` with a separator at its end, for telling whether another path is under it. */
79
+ function withSeparator(path) {
80
+ return path.endsWith(sep) ? path : `${path}${sep}`
81
+ }
82
+
83
+ function realpathOrNull(path) {
84
+ try {
85
+ return realpathSync(path)
86
+ } catch {
87
+ return null
88
+ }
89
+ }
90
+
91
+ /**
92
+ * Where `absolute` is looked at from: the root as given when it is under it, or
93
+ * else the shortest of its own leading directories that is the root under
94
+ * another spelling - through a symlink above the root, like /tmp on macOS -
95
+ * whatever comes after that one being looked at part by part; null when none is.
96
+ */
97
+ function spelledRoot(absolute, rootPath, rootReal) {
98
+ if (absolute.startsWith(withSeparator(rootPath))) return rootPath
99
+ const leading = []
100
+ for (let path = dirname(absolute); ; path = dirname(path)) {
101
+ leading.unshift(path)
102
+ if (dirname(path) === path) break
103
+ }
104
+ for (const path of leading) {
105
+ try {
106
+ if (realpathSync(path) === rootReal) return path
107
+ } catch {
108
+ return null
109
+ }
110
+ }
111
+ return null
112
+ }
113
+
114
+ /**
115
+ * What is wrong with changing `target` under `root`, or null when nothing is.
116
+ * A target spelled with another path to the root - through a symlink above it,
117
+ * like /tmp on macOS - is looked at from where that path reaches the root.
118
+ *
119
+ * @param {string} root - The directory the change may be made under
120
+ * @param {string} target - An absolute path, or one from the root
121
+ * @param {{ writesInto?: boolean, root?: boolean }} [options] - `writesInto` when an existing file at `target` is written
122
+ * into; `root` when the root itself may be the target, as for creating a directory that is there already
123
+ * @returns {{ target: string, path: string, problem: string } | null} The target and the path the problem is at, both
124
+ * from the root, and the problem
125
+ */
126
+ export function unsafeWriteProblem(root, target, { writesInto = false, root: rootAllowed = false } = {}) {
127
+ const rootPath = resolve(root)
128
+ const absolute = resolve(rootPath, target)
129
+ let rootReal
130
+ try {
131
+ rootReal = realpathSync(rootPath)
132
+ } catch {
133
+ return { target: absolute, path: rootPath, problem: "can't be resolved" }
134
+ }
135
+ if (absolute === rootPath) {
136
+ return rootAllowed ? null : { target: '.', path: '.', problem: 'is the root itself, which is not changed' }
137
+ }
138
+ const base = spelledRoot(absolute, rootPath, rootReal)
139
+ if (base === null && rootAllowed && realpathOrNull(absolute) === rootReal) return null
140
+ const along = base === null ? null : relative(base, absolute)
141
+ if (along === null || along === '' || isAbsolute(along) || along === '..' || along.startsWith(`..${sep}`)) {
142
+ return { target: absolute, path: absolute, problem: `is outside ${rootPath}` }
143
+ }
144
+ const shownTarget = fromBase(base, absolute)
145
+
146
+ let deepest = base
147
+ let current = base
148
+ const parts = along.split(sep)
149
+ for (let depth = 0; depth < parts.length; depth++) {
150
+ current = join(current, parts[depth])
151
+ let stat
152
+ try {
153
+ stat = lstatSync(current)
154
+ } catch (error) {
155
+ // Nothing is there yet, or a file stands where a directory goes and the call itself fails
156
+ if (error.code === 'ENOENT' || error.code === 'ENOTDIR') break
157
+ return { target: shownTarget, path: fromBase(base, current), problem: "can't be looked at" }
158
+ }
159
+ if (stat.isSymbolicLink()) {
160
+ return { target: shownTarget, path: fromBase(base, current), problem: 'is a symlink' }
161
+ }
162
+ deepest = current
163
+ if (depth === parts.length - 1) {
164
+ if (writesInto && stat.isFile() && stat.nlink > 1) {
165
+ return { target: shownTarget, path: shownTarget, problem: 'has other hard links' }
166
+ }
167
+ } else if (!stat.isDirectory()) {
168
+ break
169
+ }
170
+ }
171
+
172
+ let deepestReal
173
+ try {
174
+ deepestReal = realpathSync(deepest)
175
+ } catch {
176
+ return { target: shownTarget, path: fromBase(base, deepest), problem: "can't be resolved" }
177
+ }
178
+ if (deepestReal !== rootReal && !deepestReal.startsWith(withSeparator(rootReal))) {
179
+ return { target: shownTarget, path: fromBase(base, deepest), problem: `resolves outside ${rootReal}` }
180
+ }
181
+ return null
182
+ }
183
+
184
+ /** The error a refused call throws. */
185
+ function refusal(operation, root, { target, path, problem }) {
186
+ const error = new Error(
187
+ `Refused to ${operation} ${target}: ${path} ${problem}, and nothing was written. ` +
188
+ 'What is written or removed through a symlink, a hard link or a path out of the project lands outside it: ' +
189
+ 'make each part of the path a file or directory of its own inside the project, and run the command again.'
190
+ )
191
+ return Object.assign(error, { code: UNSAFE_WRITE, root: resolve(root), target, path, problem })
192
+ }
193
+
194
+ /** `target` as an absolute path under `root`, once changing it is safe; throws the refusal otherwise. */
195
+ function checked(operation, root, target, options) {
196
+ const problem = unsafeWriteProblem(root, target, options)
197
+ if (problem) throw refusal(operation, root, problem)
198
+ return resolve(resolve(root), target)
199
+ }
200
+
201
+ /** The flags a writeFile call opens with: the ones the generator uses, never following a symlink. */
202
+ function writeOptions(options) {
203
+ const normalized = typeof options === 'string' ? { encoding: options } : { ...options }
204
+ const flag = normalized.flag ?? 'w'
205
+ if (!(flag in WRITE_FLAGS)) {
206
+ throw new Error(`safe-fs writes with the flags ${Object.keys(WRITE_FLAGS).join(' and ')}, not ${flag}`)
207
+ }
208
+ return { ...normalized, flag: WRITE_FLAGS[flag] }
209
+ }
210
+
211
+ /**
212
+ * The calls that change things under `root`, each refusing - with an error whose
213
+ * code is UNSAFE_WRITE, before touching anything - what `unsafeWriteProblem`
214
+ * finds wrong with its path. Paths are absolute, or from the root.
215
+ *
216
+ * @param {string} root - The directory these calls may change things under
217
+ */
218
+ export function projectFiles(root) {
219
+ const writeFile = (path, data, options) => {
220
+ writeFileSync(checked('write', root, path, { writesInto: true }), data, writeOptions(options))
221
+ }
222
+ const mkdir = (path, options) => mkdirSync(checked('create', root, path, { root: true }), options)
223
+ const mkdtemp = prefix => {
224
+ checked('create a directory beside', root, prefix)
225
+ return mkdtempSync(resolve(resolve(root), prefix))
226
+ }
227
+ const copyFile = (source, destination, mode = 0) => {
228
+ if (mode & ~KNOWN_COPY_MODE) {
229
+ throw new Error(`safe-fs copies with COPYFILE_EXCL or no mode, not ${mode}`)
230
+ }
231
+ const target = checked('copy to', root, destination, { writesInto: true })
232
+ const sourceFd = openSync(source, constants.O_RDONLY)
233
+ try {
234
+ const sourceMode = fstatSync(sourceFd).mode
235
+ const flag = mode & constants.COPYFILE_EXCL ? WRITE_FLAGS.wx : WRITE_FLAGS.w
236
+ const destFd = openSync(target, flag, sourceMode & 0o777)
237
+ try {
238
+ const buffer = Buffer.allocUnsafe(COPY_BLOCK_SIZE)
239
+ for (;;) {
240
+ const bytesRead = readSync(sourceFd, buffer, 0, COPY_BLOCK_SIZE, null)
241
+ if (bytesRead === 0) break
242
+ writeSync(destFd, buffer, 0, bytesRead, null)
243
+ }
244
+ fchmodSync(destFd, sourceMode & 0o777)
245
+ } finally {
246
+ closeSync(destFd)
247
+ }
248
+ } finally {
249
+ closeSync(sourceFd)
250
+ }
251
+ }
252
+ const rm = (path, options) => rmSync(checked('remove', root, path), options)
253
+ const rmdir = path => rmdirSync(checked('remove', root, path))
254
+ const unlink = path => unlinkSync(checked('remove', root, path))
255
+ const rename = (from, to) => {
256
+ const source = checked('move', root, from)
257
+ renameSync(source, checked('move to', root, to))
258
+ }
259
+
260
+ return {
261
+ root: resolve(root),
262
+ writeFileSync: writeFile,
263
+ mkdirSync: mkdir,
264
+ mkdtempSync: mkdtemp,
265
+ copyFileSync: copyFile,
266
+ rmSync: rm,
267
+ rmdirSync: rmdir,
268
+ unlinkSync: unlink,
269
+ renameSync: rename,
270
+ writeFile: async (...args) => writeFile(...args),
271
+ mkdir: async (...args) => mkdir(...args),
272
+ mkdtemp: async (...args) => mkdtemp(...args),
273
+ copyFile: async (...args) => copyFile(...args),
274
+ rm: async (...args) => rm(...args),
275
+ rmdir: async (...args) => rmdir(...args),
276
+ unlink: async (...args) => unlink(...args),
277
+ rename: async (...args) => rename(...args),
278
+ }
279
+ }
@@ -0,0 +1,44 @@
1
+ #!/usr/bin/env node
2
+
3
+ /**
4
+ * What the registry build would change in a project's app/(templates), worked
5
+ * out without writing anything. `nextspark sync:app` runs it to name those
6
+ * changes in --dry-run, and to count them before asking for confirmation.
7
+ *
8
+ * The project comes from NEXTSPARK_PROJECT_ROOT, as for registry.mjs. Stdin
9
+ * carries a JSON object of files under app/, by path from the project root, to
10
+ * read as having that content (null: removed) instead of what is on disk: what
11
+ * the sync about to run leaves there, since the build copies app/ layouts into
12
+ * the tree. The result is printed on a line of its own, after
13
+ * `nextspark-templates-plan:`, as { create, replace, remove } with paths from
14
+ * the project root.
15
+ */
16
+
17
+ // First, so what any module prints as it loads is escaped too
18
+ import '../utils/console-guard.mjs'
19
+
20
+ import { getConfig } from './registry/config.mjs'
21
+ import { discoverTemplates } from './registry/discovery/templates.mjs'
22
+ import { planMissingPages } from './registry/post-build/page-generator.mjs'
23
+ import { jsonLine, messageLines } from '../utils/index.mjs'
24
+
25
+ const RESULT_MARKER = 'nextspark-templates-plan:'
26
+
27
+ async function readAppFiles() {
28
+ if (process.stdin.isTTY) return new Map()
29
+
30
+ let input = ''
31
+ for await (const chunk of process.stdin) input += chunk
32
+ return new Map(Object.entries(input.trim() ? JSON.parse(input) : {}))
33
+ }
34
+
35
+ try {
36
+ const appFiles = await readAppFiles()
37
+ const config = getConfig()
38
+ const templates = await discoverTemplates(config)
39
+ const changes = await planMissingPages(templates, config, appFiles)
40
+ console.log(`${RESULT_MARKER}${jsonLine(changes)}`)
41
+ } catch (error) {
42
+ for (const line of messageLines(error)) console.error(line)
43
+ process.exit(1)
44
+ }
@@ -11,11 +11,13 @@
11
11
  * node scripts/build-theme.mjs --watch # Build and watch for changes
12
12
  */
13
13
 
14
- import fs from 'fs'
14
+ import { existsSync, readdirSync, readFileSync, statSync, watch } from 'fs'
15
15
  import path from 'path'
16
16
  import { fileURLToPath } from 'url'
17
17
  import dotenv from 'dotenv'
18
18
  import { getConfig } from './registry/config.mjs'
19
+ import { isUnsafeWrite, projectFiles } from './safe-fs.mjs'
20
+ import { rewriteBelowGeneratedTag } from '../utils/generated-tag.mjs'
19
21
 
20
22
  const __filename = fileURLToPath(import.meta.url)
21
23
  const __dirname = path.dirname(__filename)
@@ -70,8 +72,8 @@ ${expectedImport}
70
72
  `
71
73
 
72
74
  // Check if file exists and has correct import
73
- if (fs.existsSync(appGlobalsCssPath)) {
74
- const currentContent = fs.readFileSync(appGlobalsCssPath, 'utf8')
75
+ if (existsSync(appGlobalsCssPath)) {
76
+ const currentContent = readFileSync(appGlobalsCssPath, 'utf8')
75
77
 
76
78
  // Extract current import (handle various formats)
77
79
  const importMatch = currentContent.match(/@import\s+["']([^"']+)["'];?/)
@@ -84,20 +86,21 @@ ${expectedImport}
84
86
  return false // Already correct, no update needed
85
87
  }
86
88
 
87
- // Update only the import line, preserving any custom comments
88
- const updatedContent = currentContent.replace(
89
- /@import\s+["'][^"']+["'];?/,
90
- expectedImport
89
+ // Update only the import line, preserving any custom comments. A generated
90
+ // tag sync:app put on the file keeps telling the truth: rehashed when the
91
+ // file still matched it, left as it was when the project had changed it.
92
+ const updatedContent = rewriteBelowGeneratedTag(currentContent, body =>
93
+ body.replace(/@import\s+["'][^"']+["'];?/, expectedImport)
91
94
  )
92
95
 
93
- fs.writeFileSync(appGlobalsCssPath, updatedContent)
96
+ projectFiles(config.projectRoot).writeFileSync(appGlobalsCssPath, updatedContent)
94
97
  console.log(` 🔄 Updated app/globals.css import: ${relativePath}`)
95
98
  return true
96
99
  }
97
100
  }
98
101
 
99
102
  // File doesn't exist or has no import - write full template
100
- fs.writeFileSync(appGlobalsCssPath, template)
103
+ projectFiles(config.projectRoot).writeFileSync(appGlobalsCssPath, template)
101
104
  console.log(` ✅ Created app/globals.css with import: ${relativePath}`)
102
105
  return true
103
106
  }
@@ -106,16 +109,16 @@ ${expectedImport}
106
109
  * Recursively copy directory contents
107
110
  */
108
111
  function copyRecursive(src, dest) {
109
- const stats = fs.statSync(src)
112
+ const stats = statSync(src)
110
113
 
111
114
  if (stats.isDirectory()) {
112
115
  // Create directory if it doesn't exist
113
- if (!fs.existsSync(dest)) {
114
- fs.mkdirSync(dest, { recursive: true })
116
+ if (!existsSync(dest)) {
117
+ projectFiles(rootDir).mkdirSync(dest, { recursive: true })
115
118
  }
116
119
 
117
120
  // Read directory contents
118
- const entries = fs.readdirSync(src, { withFileTypes: true })
121
+ const entries = readdirSync(src, { withFileTypes: true })
119
122
 
120
123
  for (const entry of entries) {
121
124
  const srcPath = path.join(src, entry.name)
@@ -124,11 +127,11 @@ function copyRecursive(src, dest) {
124
127
  if (entry.isDirectory()) {
125
128
  copyRecursive(srcPath, destPath)
126
129
  } else {
127
- fs.copyFileSync(srcPath, destPath)
130
+ projectFiles(rootDir).copyFileSync(srcPath, destPath)
128
131
  }
129
132
  }
130
133
  } else {
131
- fs.copyFileSync(src, dest)
134
+ projectFiles(rootDir).copyFileSync(src, dest)
132
135
  }
133
136
  }
134
137
 
@@ -138,11 +141,11 @@ function copyRecursive(src, dest) {
138
141
  function countFiles(dir) {
139
142
  let count = 0
140
143
 
141
- if (!fs.existsSync(dir)) {
144
+ if (!existsSync(dir)) {
142
145
  return 0
143
146
  }
144
147
 
145
- const entries = fs.readdirSync(dir, { withFileTypes: true })
148
+ const entries = readdirSync(dir, { withFileTypes: true })
146
149
 
147
150
  for (const entry of entries) {
148
151
  if (entry.isDirectory()) {
@@ -163,32 +166,32 @@ function copyBlockThumbnails(themePath, activeTheme) {
163
166
  const blocksPath = path.join(themePath, 'blocks')
164
167
  const publicBlocksPath = path.join(rootDir, 'public', 'theme', 'blocks')
165
168
 
166
- if (!fs.existsSync(blocksPath)) {
169
+ if (!existsSync(blocksPath)) {
167
170
  return 0
168
171
  }
169
172
 
170
173
  // Ensure public/theme/blocks directory exists
171
- if (!fs.existsSync(publicBlocksPath)) {
172
- fs.mkdirSync(publicBlocksPath, { recursive: true })
174
+ if (!existsSync(publicBlocksPath)) {
175
+ projectFiles(rootDir).mkdirSync(publicBlocksPath, { recursive: true })
173
176
  }
174
177
 
175
178
  let copiedCount = 0
176
- const blockDirs = fs.readdirSync(blocksPath, { withFileTypes: true })
179
+ const blockDirs = readdirSync(blocksPath, { withFileTypes: true })
177
180
  .filter(dirent => dirent.isDirectory())
178
181
 
179
182
  for (const blockDir of blockDirs) {
180
183
  const thumbnailSrc = path.join(blocksPath, blockDir.name, 'thumbnail.png')
181
184
 
182
- if (fs.existsSync(thumbnailSrc)) {
185
+ if (existsSync(thumbnailSrc)) {
183
186
  const thumbnailDestDir = path.join(publicBlocksPath, blockDir.name)
184
187
  const thumbnailDest = path.join(thumbnailDestDir, 'thumbnail.png')
185
188
 
186
189
  // Create destination directory if needed
187
- if (!fs.existsSync(thumbnailDestDir)) {
188
- fs.mkdirSync(thumbnailDestDir, { recursive: true })
190
+ if (!existsSync(thumbnailDestDir)) {
191
+ projectFiles(rootDir).mkdirSync(thumbnailDestDir, { recursive: true })
189
192
  }
190
193
 
191
- fs.copyFileSync(thumbnailSrc, thumbnailDest)
194
+ projectFiles(rootDir).copyFileSync(thumbnailSrc, thumbnailDest)
192
195
  copiedCount++
193
196
  }
194
197
  }
@@ -205,11 +208,11 @@ function copyThemePublicAssets(themePath, activeTheme) {
205
208
  const publicThemePath = path.join(rootDir, 'public', 'theme')
206
209
 
207
210
  // Ensure public/theme directory exists
208
- if (!fs.existsSync(publicThemePath)) {
209
- fs.mkdirSync(publicThemePath, { recursive: true })
211
+ if (!existsSync(publicThemePath)) {
212
+ projectFiles(rootDir).mkdirSync(publicThemePath, { recursive: true })
210
213
  }
211
214
 
212
- if (!fs.existsSync(themePublicPath)) {
215
+ if (!existsSync(themePublicPath)) {
213
216
  console.log(` ℹ️ No public assets directory found for theme: ${activeTheme}`)
214
217
  return
215
218
  }
@@ -225,6 +228,7 @@ function copyThemePublicAssets(themePath, activeTheme) {
225
228
  console.log(` ✅ Copied ${fileCount} asset(s) to public/theme/`)
226
229
  }
227
230
  } catch (error) {
231
+ if (isUnsafeWrite(error)) throw error
228
232
  console.error(` ❌ Failed to copy public assets:`, error)
229
233
  }
230
234
  }
@@ -279,8 +283,8 @@ export async function buildTheme(projectRoot = null) {
279
283
  const themePath = path.join(config.contentsDir, 'themes', activeTheme)
280
284
 
281
285
  // Ensure output directory exists
282
- if (!fs.existsSync(outputDir)) {
283
- fs.mkdirSync(outputDir, { recursive: true })
286
+ if (!existsSync(outputDir)) {
287
+ projectFiles(config.projectRoot).mkdirSync(outputDir, { recursive: true })
284
288
  }
285
289
 
286
290
  // Sync app/globals.css to import from the active theme
@@ -288,7 +292,7 @@ export async function buildTheme(projectRoot = null) {
288
292
  syncAppGlobalsCss(config, activeTheme)
289
293
 
290
294
  // Copy theme public assets (for all themes, including default)
291
- if (fs.existsSync(themePath)) {
295
+ if (existsSync(themePath)) {
292
296
  copyThemePublicAssets(themePath, activeTheme)
293
297
 
294
298
  // Copy block thumbnails to public/theme/blocks/
@@ -299,22 +303,22 @@ export async function buildTheme(projectRoot = null) {
299
303
  }
300
304
 
301
305
  // Build theme (including default - all themes load their own styles)
302
- if (!fs.existsSync(themePath)) {
306
+ if (!existsSync(themePath)) {
303
307
  console.error(`❌ Theme directory not found: ${themePath}`)
304
308
  console.log('📁 Available themes:')
305
309
 
306
310
  const themesDir = path.join(rootDir, 'contents', 'themes')
307
- const availableThemes = fs.readdirSync(themesDir)
311
+ const availableThemes = readdirSync(themesDir)
308
312
  .filter(item => {
309
313
  const itemPath = path.join(themesDir, item)
310
- return fs.statSync(itemPath).isDirectory() && item !== 'node_modules'
314
+ return statSync(itemPath).isDirectory() && item !== 'node_modules'
311
315
  })
312
316
 
313
317
  availableThemes.forEach(theme => console.log(` - ${theme}`))
314
318
 
315
319
  // Fallback to empty (core styles only)
316
320
  console.log('🔄 Falling back to core styles only')
317
- fs.writeFileSync(outputPath, `/* Fallback: Theme '${activeTheme}' not found, using core styles */\n`)
321
+ projectFiles(config.projectRoot).writeFileSync(outputPath, `/* Fallback: Theme '${activeTheme}' not found, using core styles */\n`)
318
322
  return
319
323
  }
320
324
 
@@ -328,8 +332,8 @@ export async function buildTheme(projectRoot = null) {
328
332
  let globalCSS = ''
329
333
  let componentCSS = ''
330
334
 
331
- if (fs.existsSync(globalStylesPath)) {
332
- globalCSS = fs.readFileSync(globalStylesPath, 'utf8')
335
+ if (existsSync(globalStylesPath)) {
336
+ globalCSS = readFileSync(globalStylesPath, 'utf8')
333
337
  console.log(` ✅ Loaded globals.css (${globalCSS.length} chars)`)
334
338
 
335
339
  // Validate that theme has all required sections
@@ -338,8 +342,8 @@ export async function buildTheme(projectRoot = null) {
338
342
  console.warn(` ⚠️ globals.css not found: ${globalStylesPath}`)
339
343
  }
340
344
 
341
- if (fs.existsSync(componentStylesPath)) {
342
- componentCSS = fs.readFileSync(componentStylesPath, 'utf8')
345
+ if (existsSync(componentStylesPath)) {
346
+ componentCSS = readFileSync(componentStylesPath, 'utf8')
343
347
  console.log(` ✅ Loaded components.css (${componentCSS.length} chars)`)
344
348
  } else {
345
349
  console.warn(` ⚠️ components.css not found: ${componentStylesPath}`)
@@ -362,7 +366,7 @@ ${componentCSS}
362
366
  `
363
367
 
364
368
  // Write final CSS to .next directory (for build cache)
365
- fs.writeFileSync(outputPath, finalCSS)
369
+ projectFiles(config.projectRoot).writeFileSync(outputPath, finalCSS)
366
370
 
367
371
  console.log(`✅ Theme built successfully!`)
368
372
  console.log(`📄 Output: ${outputPath} (${finalCSS.length} chars)`)
@@ -379,7 +383,7 @@ ${componentCSS}
379
383
  * Falling back to core styles only.
380
384
  */
381
385
  `
382
- fs.writeFileSync(outputPath, errorCSS)
386
+ projectFiles(config.projectRoot).writeFileSync(outputPath, errorCSS)
383
387
 
384
388
  throw error
385
389
  }
@@ -399,7 +403,7 @@ async function watchTheme() {
399
403
  let debounceTimer = null
400
404
 
401
405
  try {
402
- const watcher = fs.watch(stylesPath, { recursive: true }, (eventType, filename) => {
406
+ const watcher = watch(stylesPath, { recursive: true }, (eventType, filename) => {
403
407
  if (filename && (filename.endsWith('.css'))) {
404
408
  clearTimeout(debounceTimer)
405
409
  debounceTimer = setTimeout(async () => {
@@ -15,10 +15,11 @@
15
15
  * Usage: node core/scripts/build/update-tsconfig.mjs
16
16
  */
17
17
 
18
- import fs from 'fs'
18
+ import { existsSync, readdirSync, readFileSync, statSync } from 'fs'
19
19
  import path from 'path'
20
20
  import { fileURLToPath } from 'url'
21
21
  import dotenv from 'dotenv'
22
+ import { projectFiles } from './safe-fs.mjs'
22
23
 
23
24
  const __filename = fileURLToPath(import.meta.url)
24
25
  const __dirname = path.dirname(__filename)
@@ -44,14 +45,14 @@ const PLUGINS_DIR = path.join(ROOT_DIR, 'contents/plugins')
44
45
  * Get all theme directories
45
46
  */
46
47
  function getAllThemes() {
47
- if (!fs.existsSync(THEMES_DIR)) {
48
+ if (!existsSync(THEMES_DIR)) {
48
49
  return []
49
50
  }
50
51
 
51
- return fs.readdirSync(THEMES_DIR)
52
+ return readdirSync(THEMES_DIR)
52
53
  .filter(item => {
53
54
  const themePath = path.join(THEMES_DIR, item)
54
- return fs.statSync(themePath).isDirectory()
55
+ return statSync(themePath).isDirectory()
55
56
  })
56
57
  }
57
58
 
@@ -59,14 +60,14 @@ function getAllThemes() {
59
60
  * Get all plugin directories
60
61
  */
61
62
  function getAllPlugins() {
62
- if (!fs.existsSync(PLUGINS_DIR)) {
63
+ if (!existsSync(PLUGINS_DIR)) {
63
64
  return []
64
65
  }
65
66
 
66
- return fs.readdirSync(PLUGINS_DIR)
67
+ return readdirSync(PLUGINS_DIR)
67
68
  .filter(item => {
68
69
  const pluginPath = path.join(PLUGINS_DIR, item)
69
- return fs.statSync(pluginPath).isDirectory()
70
+ return statSync(pluginPath).isDirectory()
70
71
  })
71
72
  }
72
73
 
@@ -76,20 +77,20 @@ function getAllPlugins() {
76
77
  function getActivePlugins(activeTheme) {
77
78
  const themeConfigPath = path.join(THEMES_DIR, activeTheme, 'theme.config.ts')
78
79
 
79
- if (!fs.existsSync(themeConfigPath)) {
80
+ if (!existsSync(themeConfigPath)) {
80
81
  // Try .js extension
81
82
  const jsConfigPath = path.join(THEMES_DIR, activeTheme, 'theme.config.js')
82
- if (!fs.existsSync(jsConfigPath)) {
83
+ if (!existsSync(jsConfigPath)) {
83
84
  return []
84
85
  }
85
86
  }
86
87
 
87
88
  try {
88
- const configPath = fs.existsSync(path.join(THEMES_DIR, activeTheme, 'theme.config.ts'))
89
+ const configPath = existsSync(path.join(THEMES_DIR, activeTheme, 'theme.config.ts'))
89
90
  ? path.join(THEMES_DIR, activeTheme, 'theme.config.ts')
90
91
  : path.join(THEMES_DIR, activeTheme, 'theme.config.js')
91
92
 
92
- const configContent = fs.readFileSync(configPath, 'utf8')
93
+ const configContent = readFileSync(configPath, 'utf8')
93
94
 
94
95
  // Match plugins array: plugins: ['plugin1', 'plugin2']
95
96
  const pluginsMatch = configContent.match(/plugins:\s*\[(.*?)\]/s)
@@ -119,14 +120,14 @@ function generateTsConfig() {
119
120
  const activeTheme = process.env.NEXT_PUBLIC_ACTIVE_THEME
120
121
 
121
122
  // Check if base template exists
122
- if (!fs.existsSync(TSCONFIG_BASE_PATH)) {
123
+ if (!existsSync(TSCONFIG_BASE_PATH)) {
123
124
  console.error('❌ tsconfig.base.json not found!')
124
125
  console.error(' Please ensure tsconfig.base.json exists in the project root.')
125
126
  process.exit(1)
126
127
  }
127
128
 
128
129
  // Read base template
129
- const baseConfig = JSON.parse(fs.readFileSync(TSCONFIG_BASE_PATH, 'utf8'))
130
+ const baseConfig = JSON.parse(readFileSync(TSCONFIG_BASE_PATH, 'utf8'))
130
131
 
131
132
  // In NPM mode, add @nextspark/* aliases for registry and core resolution
132
133
  if (isNpmPackage) {
@@ -178,7 +179,7 @@ function generateTsConfig() {
178
179
  console.warn('⚠️ NEXT_PUBLIC_ACTIVE_THEME not set')
179
180
  console.warn(' Using base excludes only (all themes/plugins will be checked)')
180
181
  baseConfig.exclude = baseExcludes
181
- fs.writeFileSync(TSCONFIG_PATH, JSON.stringify(baseConfig, null, 2) + '\n', 'utf8')
182
+ projectFiles(ROOT_DIR).writeFileSync(TSCONFIG_PATH, JSON.stringify(baseConfig, null, 2) + '\n', 'utf8')
182
183
  console.log('📝 Generated tsconfig.json (no active theme)')
183
184
  return
184
185
  }
@@ -200,7 +201,7 @@ function generateTsConfig() {
200
201
  baseConfig.exclude = [...baseExcludes, ...themeExcludes, ...pluginExcludes]
201
202
 
202
203
  // Write generated tsconfig
203
- fs.writeFileSync(TSCONFIG_PATH, JSON.stringify(baseConfig, null, 2) + '\n', 'utf8')
204
+ projectFiles(ROOT_DIR).writeFileSync(TSCONFIG_PATH, JSON.stringify(baseConfig, null, 2) + '\n', 'utf8')
204
205
 
205
206
  console.log('📝 Generated tsconfig.json')
206
207
  console.log(` Active theme: ${activeTheme}`)