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

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (456) hide show
  1. package/dist/components/api/keys/CreateApiKeyDialog.d.ts.map +1 -1
  2. package/dist/components/api/keys/CreateApiKeyDialog.js +2 -1
  3. package/dist/components/app/layouts/PublicFooter.d.ts.map +1 -1
  4. package/dist/components/app/layouts/PublicFooter.js +2 -23
  5. package/dist/components/app/misc/ThemeToggle.d.ts.map +1 -1
  6. package/dist/components/app/misc/ThemeToggle.js +2 -1
  7. package/dist/components/auth/forms/LoginForm.js +1 -1
  8. package/dist/components/auth/forms/SignupForm.d.ts.map +1 -1
  9. package/dist/components/auth/forms/SignupForm.js +2 -1
  10. package/dist/components/auth/pages/AuthErrorPage.d.ts.map +1 -1
  11. package/dist/components/auth/pages/AuthErrorPage.js +2 -1
  12. package/dist/components/billing/ManageBillingButton.d.ts.map +1 -1
  13. package/dist/components/billing/ManageBillingButton.js +2 -1
  14. package/dist/components/dashboard/block-editor/array-field.d.ts.map +1 -1
  15. package/dist/components/dashboard/block-editor/array-field.js +2 -1
  16. package/dist/components/dashboard/block-editor/block-picker.d.ts.map +1 -1
  17. package/dist/components/dashboard/block-editor/block-picker.js +2 -1
  18. package/dist/components/dashboard/block-editor/builder-editor-view.d.ts.map +1 -1
  19. package/dist/components/dashboard/block-editor/builder-editor-view.js +3 -2
  20. package/dist/components/dashboard/block-editor/config-panel.d.ts.map +1 -1
  21. package/dist/components/dashboard/block-editor/config-panel.js +3 -2
  22. package/dist/components/dashboard/block-editor/dynamic-form.d.ts.map +1 -1
  23. package/dist/components/dashboard/block-editor/dynamic-form.js +2 -1
  24. package/dist/components/dashboard/block-editor/entity-fields-sidebar.d.ts.map +1 -1
  25. package/dist/components/dashboard/block-editor/entity-fields-sidebar.js +3 -2
  26. package/dist/components/dashboard/block-editor/pattern-reference-preview.d.ts.map +1 -1
  27. package/dist/components/dashboard/block-editor/pattern-reference-preview.js +2 -1
  28. package/dist/components/dashboard/layouts/TopNavbar.d.ts.map +1 -1
  29. package/dist/components/dashboard/layouts/TopNavbar.js +4 -3
  30. package/dist/components/dashboard/misc/SearchDropdown.d.ts.map +1 -1
  31. package/dist/components/dashboard/misc/SearchDropdown.js +2 -1
  32. package/dist/components/dashboard/mobile/MobileTopBar.d.ts.map +1 -1
  33. package/dist/components/dashboard/mobile/MobileTopBar.js +2 -1
  34. package/dist/components/devtools/BlockDetailViewer.d.ts.map +1 -1
  35. package/dist/components/devtools/BlockDetailViewer.js +2 -1
  36. package/dist/components/devtools/BlocksViewer.d.ts.map +1 -1
  37. package/dist/components/devtools/BlocksViewer.js +2 -1
  38. package/dist/components/devtools/ConfigViewer.d.ts.map +1 -1
  39. package/dist/components/devtools/ConfigViewer.js +3 -2
  40. package/dist/components/devtools/TestCasesViewer.d.ts.map +1 -1
  41. package/dist/components/devtools/TestCasesViewer.js +3 -2
  42. package/dist/components/devtools/TestCoverageDashboard.d.ts.map +1 -1
  43. package/dist/components/devtools/TestCoverageDashboard.js +2 -1
  44. package/dist/components/devtools/api-explorer/ApiDocsModal.d.ts.map +1 -1
  45. package/dist/components/devtools/api-explorer/ApiDocsModal.js +0 -0
  46. package/dist/components/devtools/api-explorer/ApiExplorer.d.ts.map +1 -1
  47. package/dist/components/devtools/api-explorer/ApiExplorer.js +2 -1
  48. package/dist/components/devtools/scheduled-actions/actions-table.d.ts.map +1 -1
  49. package/dist/components/devtools/scheduled-actions/actions-table.js +3 -2
  50. package/dist/components/docs/docs-sidebar.d.ts.map +1 -1
  51. package/dist/components/docs/docs-sidebar.js +3 -2
  52. package/dist/components/entities/EntityFieldRenderer.d.ts.map +1 -1
  53. package/dist/components/entities/EntityFieldRenderer.js +2 -1
  54. package/dist/components/entities/EntityList.d.ts.map +1 -1
  55. package/dist/components/entities/EntityList.js +2 -1
  56. package/dist/components/entities/EntityTable.d.ts.map +1 -1
  57. package/dist/components/entities/EntityTable.js +3 -2
  58. package/dist/components/entities/ErrorBoundary.d.ts.map +1 -1
  59. package/dist/components/entities/ErrorBoundary.js +2 -1
  60. package/dist/components/entities/wrappers/EntityDetailWrapper.d.ts.map +1 -1
  61. package/dist/components/entities/wrappers/EntityDetailWrapper.js +3 -2
  62. package/dist/components/media/MediaCard.d.ts.map +1 -1
  63. package/dist/components/media/MediaCard.js +2 -1
  64. package/dist/components/media/MediaDetailPanel.d.ts.map +1 -1
  65. package/dist/components/media/MediaDetailPanel.js +2 -1
  66. package/dist/components/media/MediaList.d.ts.map +1 -1
  67. package/dist/components/media/MediaList.js +2 -1
  68. package/dist/components/media/MediaSelector.d.ts.map +1 -1
  69. package/dist/components/media/MediaSelector.js +2 -1
  70. package/dist/components/media/MediaUploadZone.d.ts.map +1 -1
  71. package/dist/components/media/MediaUploadZone.js +2 -1
  72. package/dist/components/public/entities/PublicEntityDetail.d.ts.map +1 -1
  73. package/dist/components/public/entities/PublicEntityDetail.js +2 -1
  74. package/dist/components/teams/TeamPendingInvitations.d.ts.map +1 -1
  75. package/dist/components/teams/TeamPendingInvitations.js +3 -2
  76. package/dist/components/ui/accordion.js +1 -1
  77. package/dist/components/ui/audio-upload.d.ts.map +1 -1
  78. package/dist/components/ui/audio-upload.js +2 -1
  79. package/dist/components/ui/avatar.d.ts +17 -1
  80. package/dist/components/ui/avatar.d.ts.map +1 -1
  81. package/dist/components/ui/avatar.js +29 -2
  82. package/dist/components/ui/badge.js +1 -1
  83. package/dist/components/ui/breadcrumb.d.ts +13 -0
  84. package/dist/components/ui/breadcrumb.d.ts.map +1 -1
  85. package/dist/components/ui/breadcrumb.js +23 -10
  86. package/dist/components/ui/button.js +1 -1
  87. package/dist/components/ui/card.js +1 -1
  88. package/dist/components/ui/checkbox.js +1 -1
  89. package/dist/components/ui/file-upload.d.ts.map +1 -1
  90. package/dist/components/ui/file-upload.js +2 -1
  91. package/dist/components/ui/image-upload.d.ts.map +1 -1
  92. package/dist/components/ui/image-upload.js +4 -3
  93. package/dist/components/ui/input.js +1 -1
  94. package/dist/components/ui/label.js +1 -1
  95. package/dist/components/ui/optimized-image.d.ts.map +1 -1
  96. package/dist/components/ui/optimized-image.js +5 -0
  97. package/dist/components/ui/pagination.d.ts +1 -1
  98. package/dist/components/ui/pagination.d.ts.map +1 -1
  99. package/dist/components/ui/pagination.js +3 -0
  100. package/dist/components/ui/progress.js +1 -1
  101. package/dist/components/ui/relation-display.d.ts.map +1 -1
  102. package/dist/components/ui/relation-display.js +3 -2
  103. package/dist/components/ui/separator.js +1 -1
  104. package/dist/components/ui/simple-relation-select.d.ts.map +1 -1
  105. package/dist/components/ui/simple-relation-select.js +7 -6
  106. package/dist/components/ui/skeleton.js +1 -1
  107. package/dist/components/ui/slider.js +1 -1
  108. package/dist/components/ui/switch.js +1 -1
  109. package/dist/components/ui/tabs.js +1 -1
  110. package/dist/components/ui/textarea.js +1 -1
  111. package/dist/components/ui/user-display.d.ts.map +1 -1
  112. package/dist/components/ui/user-display.js +2 -1
  113. package/dist/components/ui/user-select.d.ts.map +1 -1
  114. package/dist/components/ui/user-select.js +2 -1
  115. package/dist/components/ui/video-upload.d.ts.map +1 -1
  116. package/dist/components/ui/video-upload.js +3 -2
  117. package/dist/components/users/tables/UsersTable.d.ts.map +1 -1
  118. package/dist/components/users/tables/UsersTable.js +3 -2
  119. package/dist/contexts/SubscriptionContext.d.ts.map +1 -1
  120. package/dist/contexts/SubscriptionContext.js +4 -3
  121. package/dist/contexts/TeamContext.d.ts.map +1 -1
  122. package/dist/contexts/TeamContext.js +3 -2
  123. package/dist/hooks/useAccountPreferencesSync.d.ts.map +1 -1
  124. package/dist/hooks/useAccountPreferencesSync.js +2 -1
  125. package/dist/hooks/useApiExplorerNavigation.d.ts.map +1 -1
  126. package/dist/hooks/useApiExplorerNavigation.js +2 -1
  127. package/dist/hooks/useAuth.d.ts.map +1 -1
  128. package/dist/hooks/useAuth.js +3 -2
  129. package/dist/hooks/useEnsureUserMetadata.d.ts.map +1 -1
  130. package/dist/hooks/useEnsureUserMetadata.js +3 -2
  131. package/dist/hooks/useEntityWithMeta.d.ts.map +1 -1
  132. package/dist/hooks/useEntityWithMeta.js +3 -2
  133. package/dist/hooks/useInvoice.d.ts.map +1 -1
  134. package/dist/hooks/useInvoice.js +2 -1
  135. package/dist/hooks/useInvoices.d.ts.map +1 -1
  136. package/dist/hooks/useInvoices.js +2 -1
  137. package/dist/hooks/useMedia.d.ts.map +1 -1
  138. package/dist/hooks/useMedia.js +10 -9
  139. package/dist/hooks/useMediaUpload.d.ts.map +1 -1
  140. package/dist/hooks/useMediaUpload.js +2 -1
  141. package/dist/hooks/useMembership.d.ts.map +1 -1
  142. package/dist/hooks/useMembership.js +3 -2
  143. package/dist/hooks/usePatternUsages.d.ts.map +1 -1
  144. package/dist/hooks/usePatternUsages.js +3 -2
  145. package/dist/hooks/usePrefetch.d.ts.map +1 -1
  146. package/dist/hooks/usePrefetch.js +3 -2
  147. package/dist/hooks/useQuota.d.ts.map +1 -1
  148. package/dist/hooks/useQuota.js +2 -1
  149. package/dist/hooks/useTeamInvitations.d.ts.map +1 -1
  150. package/dist/hooks/useTeamInvitations.js +4 -3
  151. package/dist/hooks/useTeamMembers.d.ts.map +1 -1
  152. package/dist/hooks/useTeamMembers.js +5 -4
  153. package/dist/hooks/useTeamUpdate.d.ts.map +1 -1
  154. package/dist/hooks/useTeamUpdate.js +2 -1
  155. package/dist/hooks/useUserProfile.d.ts.map +1 -1
  156. package/dist/hooks/useUserProfile.js +3 -2
  157. package/dist/hooks/useUserSettings.d.ts.map +1 -1
  158. package/dist/hooks/useUserSettings.js +3 -2
  159. package/dist/lib/actions/entity.actions.d.ts.map +1 -1
  160. package/dist/lib/actions/entity.actions.js +2 -1
  161. package/dist/lib/api/entities.d.ts.map +1 -1
  162. package/dist/lib/api/entities.js +4 -3
  163. package/dist/lib/auth/verify-email-link.d.ts +10 -0
  164. package/dist/lib/auth/verify-email-link.d.ts.map +1 -0
  165. package/dist/lib/auth/verify-email-link.js +14 -0
  166. package/dist/lib/auth-client.d.ts +2 -0
  167. package/dist/lib/auth-client.d.ts.map +1 -1
  168. package/dist/lib/auth-client.js +4 -0
  169. package/dist/lib/auth.d.ts.map +1 -1
  170. package/dist/lib/auth.js +10 -2
  171. package/dist/lib/base-path.d.ts +85 -0
  172. package/dist/lib/base-path.d.ts.map +1 -0
  173. package/dist/lib/base-path.js +79 -0
  174. package/dist/lib/billing/schema.d.ts +1 -1
  175. package/dist/lib/billing/schema.d.ts.map +1 -1
  176. package/dist/lib/billing/schema.js +1 -1
  177. package/dist/lib/blocks/sanitize-html.d.ts.map +1 -1
  178. package/dist/lib/blocks/sanitize-html.js +18 -2
  179. package/dist/lib/cache/revalidate-tag.d.ts +14 -0
  180. package/dist/lib/cache/revalidate-tag.d.ts.map +1 -0
  181. package/dist/lib/cache/revalidate-tag.js +8 -0
  182. package/dist/lib/config/dashboard-schema.d.ts +1 -1
  183. package/dist/lib/config/dashboard-schema.d.ts.map +1 -1
  184. package/dist/lib/config/dashboard-schema.js +1 -1
  185. package/dist/lib/config/types.d.ts +36 -16
  186. package/dist/lib/config/types.d.ts.map +1 -1
  187. package/dist/lib/docs/access.d.ts +28 -0
  188. package/dist/lib/docs/access.d.ts.map +1 -0
  189. package/dist/lib/docs/access.js +16 -0
  190. package/dist/lib/docs/parser.d.ts.map +1 -1
  191. package/dist/lib/docs/parser.js +2 -1
  192. package/dist/lib/docs/remark-doc-links.d.ts +14 -0
  193. package/dist/lib/docs/remark-doc-links.d.ts.map +1 -0
  194. package/dist/lib/docs/remark-doc-links.js +38 -0
  195. package/dist/lib/entities/api-generator.d.ts +1 -1
  196. package/dist/lib/entities/api-generator.d.ts.map +1 -1
  197. package/dist/lib/entities/api-generator.js +1 -1
  198. package/dist/lib/entities/schema-generator.d.ts +1 -1
  199. package/dist/lib/entities/schema-generator.d.ts.map +1 -1
  200. package/dist/lib/entities/schema-generator.js +1 -1
  201. package/dist/lib/mcp/engine.js +1 -1
  202. package/dist/lib/mcp/schema-builder.d.ts +1 -1
  203. package/dist/lib/mcp/schema-builder.d.ts.map +1 -1
  204. package/dist/lib/mcp/schema-builder.js +1 -1
  205. package/dist/lib/mcp/tool-generator.js +1 -1
  206. package/dist/lib/mcp/types.d.ts +1 -1
  207. package/dist/lib/mcp/types.d.ts.map +1 -1
  208. package/dist/lib/media/schemas.d.ts +1 -1
  209. package/dist/lib/media/schemas.d.ts.map +1 -1
  210. package/dist/lib/media/schemas.js +1 -1
  211. package/dist/lib/middleware/index.d.ts +5 -1
  212. package/dist/lib/middleware/index.d.ts.map +1 -1
  213. package/dist/lib/middleware/index.js +3 -2
  214. package/dist/lib/services/meta.service.d.ts +60 -0
  215. package/dist/lib/services/meta.service.d.ts.map +1 -1
  216. package/dist/lib/services/meta.service.js +132 -12
  217. package/dist/lib/services/middleware.service.d.ts.map +1 -1
  218. package/dist/lib/services/middleware.service.js +3 -2
  219. package/dist/lib/teams/schema.d.ts +1 -1
  220. package/dist/lib/teams/schema.d.ts.map +1 -1
  221. package/dist/lib/teams/schema.js +1 -1
  222. package/dist/lib/teams/teams.api.d.ts.map +1 -1
  223. package/dist/lib/teams/teams.api.js +4 -3
  224. package/dist/lib/user-data-client.d.ts.map +1 -1
  225. package/dist/lib/user-data-client.js +3 -2
  226. package/dist/lib/validation/invoices.d.ts +1 -1
  227. package/dist/lib/validation/invoices.d.ts.map +1 -1
  228. package/dist/lib/validation/invoices.js +1 -1
  229. package/dist/lib/validation-schemas.d.ts +1 -1
  230. package/dist/lib/validation-schemas.d.ts.map +1 -1
  231. package/dist/lib/validation-schemas.js +1 -1
  232. package/dist/lib/validation.d.ts +1 -1
  233. package/dist/lib/validation.d.ts.map +1 -1
  234. package/dist/lib/validation.js +1 -1
  235. package/dist/messages/de/footer.json +0 -1
  236. package/dist/messages/de/index.d.ts +0 -1
  237. package/dist/messages/de/index.d.ts.map +1 -1
  238. package/dist/messages/en/footer.json +0 -1
  239. package/dist/messages/en/index.d.ts +0 -1
  240. package/dist/messages/en/index.d.ts.map +1 -1
  241. package/dist/messages/es/footer.json +0 -1
  242. package/dist/messages/es/index.d.ts +0 -1
  243. package/dist/messages/es/index.d.ts.map +1 -1
  244. package/dist/messages/fr/footer.json +0 -1
  245. package/dist/messages/fr/index.d.ts +0 -1
  246. package/dist/messages/fr/index.d.ts.map +1 -1
  247. package/dist/messages/it/footer.json +0 -1
  248. package/dist/messages/it/index.d.ts +0 -1
  249. package/dist/messages/it/index.d.ts.map +1 -1
  250. package/dist/messages/pt/footer.json +0 -1
  251. package/dist/messages/pt/index.d.ts +0 -1
  252. package/dist/messages/pt/index.d.ts.map +1 -1
  253. package/dist/templates/app/(auth)/accept-invite/[token]/page.tsx +4 -3
  254. package/dist/templates/app/(auth)/verify-email/page.tsx +4 -3
  255. package/dist/templates/app/(public)/docs/[section]/[page]/page.tsx +11 -0
  256. package/dist/templates/app/api/auth/[...all]/route.ts +15 -14
  257. package/dist/templates/app/api/internal/user-metadata/route.ts +6 -8
  258. package/dist/templates/app/api/user/plan-flags/route.ts +1 -1
  259. package/dist/templates/app/api/user/profile/route.ts +12 -17
  260. package/dist/templates/app/api/v1/api-keys/route.ts +1 -1
  261. package/dist/templates/app/api/v1/auth/signup-with-invite/route.ts +2 -1
  262. package/dist/templates/app/api/v1/billing/cancel/route.ts +1 -1
  263. package/dist/templates/app/api/v1/billing/change-plan/route.ts +1 -1
  264. package/dist/templates/app/api/v1/billing/check-action/route.ts +1 -1
  265. package/dist/templates/app/api/v1/billing/checkout/route.ts +1 -1
  266. package/dist/templates/app/api/v1/blocks/validate/route.ts +1 -1
  267. package/dist/templates/app/api/v1/media/[id]/tags/route.ts +1 -1
  268. package/dist/templates/app/api/v1/post-categories/[id]/route.ts +1 -1
  269. package/dist/templates/app/api/v1/post-categories/route.ts +1 -1
  270. package/dist/templates/app/api/v1/teams/[teamId]/members/route.ts +2 -1
  271. package/dist/templates/app/api/v1/teams/[teamId]/subscription/route.ts +3 -5
  272. package/dist/templates/app/api/v1/teams/docs.md +4 -9
  273. package/dist/templates/app/api/v1/teams/switch/route.ts +1 -1
  274. package/dist/templates/app/api/v1/users/[id]/meta/[key]/route.ts +1 -1
  275. package/dist/templates/app/api/v1/users/[id]/route.ts +1 -1
  276. package/dist/templates/app/api/v1/users/route.ts +1 -1
  277. package/dist/templates/app/dashboard/(main)/media/page.tsx +3 -1
  278. package/dist/templates/app/dashboard/settings/api-keys/page.tsx +5 -4
  279. package/dist/templates/app/dashboard/settings/profile/page.tsx +3 -2
  280. package/dist/templates/app/devtools/scheduled-actions/page.tsx +2 -1
  281. package/dist/templates/app/superadmin/docs/[section]/[page]/page.tsx +16 -0
  282. package/dist/templates/app/superadmin/subscriptions/page.tsx +3 -2
  283. package/dist/templates/app/superadmin/teams/[teamId]/page.tsx +5 -4
  284. package/dist/templates/app/superadmin/users/[userId]/page.tsx +2 -1
  285. package/dist/templates/app/superadmin/users/page.tsx +2 -1
  286. package/dist/templates/blocks/cta-section/component.tsx +3 -2
  287. package/dist/templates/blocks/cta-section/schema.ts +1 -1
  288. package/dist/templates/blocks/features-grid/component.tsx +2 -1
  289. package/dist/templates/blocks/features-grid/schema.ts +1 -1
  290. package/dist/templates/blocks/hero/component.tsx +3 -2
  291. package/dist/templates/blocks/hero/schema.ts +1 -1
  292. package/dist/templates/blocks/testimonials/component.tsx +2 -1
  293. package/dist/templates/blocks/testimonials/schema.ts +1 -1
  294. package/dist/templates/blocks/text-content/component.tsx +2 -1
  295. package/dist/templates/blocks/text-content/schema.ts +1 -1
  296. package/dist/templates/ci-workflows/README.md +1 -1
  297. package/dist/templates/ci-workflows/github/cypress-regression.yml +1 -1
  298. package/dist/templates/ci-workflows/github/cypress-smoke.yml +1 -1
  299. package/dist/templates/contents/plugins/starter/plugin/api/example/route.ts.template +1 -1
  300. package/dist/templates/contents/themes/starter/blocks/cta-section/component.tsx +3 -2
  301. package/dist/templates/contents/themes/starter/blocks/cta-section/schema.ts +1 -1
  302. package/dist/templates/contents/themes/starter/blocks/features-grid/component.tsx +2 -1
  303. package/dist/templates/contents/themes/starter/blocks/features-grid/schema.ts +1 -1
  304. package/dist/templates/contents/themes/starter/blocks/hero/component.tsx +3 -2
  305. package/dist/templates/contents/themes/starter/blocks/hero/schema.ts +1 -1
  306. package/dist/templates/contents/themes/starter/blocks/testimonials/component.tsx +3 -2
  307. package/dist/templates/contents/themes/starter/blocks/testimonials/schema.ts +1 -1
  308. package/dist/templates/contents/themes/starter/blocks/text-content/component.tsx +2 -1
  309. package/dist/templates/contents/themes/starter/blocks/text-content/schema.ts +1 -1
  310. package/dist/templates/contents/themes/starter/entities/posts/components/post-header.tsx +2 -1
  311. package/dist/templates/contents/themes/starter/lib/hooks/useAnalyticsData.ts +2 -1
  312. package/dist/templates/contents/themes/starter/lib/hooks/useTaskStats.ts +2 -1
  313. package/dist/templates/eslint.config.mjs +133 -9
  314. package/dist/templates/features/blog/blocks/post-content/component.tsx +2 -1
  315. package/dist/templates/features/blog/blocks/post-content/schema.ts +1 -1
  316. package/dist/templates/features/blog/entities/posts/components/post-header.tsx +2 -1
  317. package/dist/templates/features/pages/blocks/hero/component.tsx +3 -2
  318. package/dist/templates/features/pages/blocks/hero/schema.ts +1 -1
  319. package/dist/templates/next.config.mjs +50 -29
  320. package/dist/templates/proxy.ts +100 -21
  321. package/dist/templates/public/sw.js +2 -2
  322. package/dist/templates/scripts/cy-run-prod.cjs +5 -5
  323. package/dist/types/blocks.d.ts +1 -1
  324. package/dist/types/blocks.d.ts.map +1 -1
  325. package/dist/types/blocks.js +1 -1
  326. package/package.json +9 -5
  327. package/scripts/build/docs-registry.mjs +7 -7
  328. package/scripts/build/registry/__tests__/billing-registry.test.mjs +43 -0
  329. package/scripts/build/registry/__tests__/build-output.test.mjs +209 -0
  330. package/scripts/build/registry/__tests__/copy-race-child.mjs +14 -0
  331. package/scripts/build/registry/__tests__/direct-fs-writes.mjs +203 -0
  332. package/scripts/build/registry/__tests__/globals-css-tag.test.mjs +66 -0
  333. package/scripts/build/registry/__tests__/icons-discovery.test.mjs +530 -29
  334. package/scripts/build/registry/__tests__/page-generator.test.mjs +211 -37
  335. package/scripts/build/registry/__tests__/published-signatures.test.mjs +239 -0
  336. package/scripts/build/registry/__tests__/route-file-rule.test.mjs +170 -0
  337. package/scripts/build/registry/__tests__/safe-fs.test.mjs +333 -0
  338. package/scripts/build/registry/__tests__/template-analysis.test.mjs +142 -0
  339. package/scripts/build/registry/__tests__/template-registry.test.mjs +279 -9
  340. package/scripts/build/registry/__tests__/templates-plan.test.mjs +128 -0
  341. package/scripts/build/registry/__tests__/templates-tree.test.mjs +367 -0
  342. package/scripts/build/registry/__tests__/write-places.test.mjs +425 -0
  343. package/scripts/build/registry/config.mjs +6 -65
  344. package/scripts/build/registry/discovery/icons.mjs +498 -43
  345. package/scripts/build/registry/discovery/templates.mjs +1 -1
  346. package/scripts/build/registry/generators/billing-registry.mjs +1 -1
  347. package/scripts/build/registry/generators/docs-registry.mjs +3 -4
  348. package/scripts/build/registry/generators/feature-registry.mjs +1 -1
  349. package/scripts/build/registry/generators/permissions-registry.mjs +2 -2
  350. package/scripts/build/registry/generators/template-registry.mjs +65 -49
  351. package/scripts/build/registry/index.mjs +1 -2
  352. package/scripts/build/registry/post-build/index.mjs +1 -1
  353. package/scripts/build/registry/post-build/own-gitignores.mjs +220 -0
  354. package/scripts/build/registry/post-build/page-generator.mjs +512 -268
  355. package/scripts/build/registry/post-build/route-cleanup.mjs +16 -13
  356. package/scripts/build/registry/post-build/test-fixtures.mjs +26 -8
  357. package/scripts/build/registry/project-mode.mjs +76 -0
  358. package/scripts/build/registry/shared/typescript-compiler.mjs +77 -0
  359. package/scripts/build/registry/validate-env.test.mjs +12 -8
  360. package/scripts/build/registry/watch.mjs +3 -53
  361. package/scripts/build/registry/write-places.mjs +228 -0
  362. package/scripts/build/registry.mjs +53 -30
  363. package/scripts/build/safe-fs.mjs +279 -0
  364. package/scripts/build/templates-plan.mjs +44 -0
  365. package/scripts/build/theme.mjs +46 -42
  366. package/scripts/build/update-tsconfig.mjs +16 -15
  367. package/scripts/db/cluster-changes.mjs +205 -0
  368. package/scripts/db/connection-time-limits.mjs +53 -0
  369. package/scripts/db/inspect-server.mjs +108 -0
  370. package/scripts/db/migration-time-limit.mjs +695 -0
  371. package/scripts/db/run-migrations.mjs +46 -41
  372. package/scripts/db/ssl-config.mjs +194 -0
  373. package/scripts/db/theme-location.mjs +69 -0
  374. package/scripts/db/verify-tables.mjs +3 -8
  375. package/scripts/db/verify-teams.mjs +2 -6
  376. package/scripts/db/verify-theme-migrations.mjs +169 -0
  377. package/scripts/maintenance/core-updater.mjs +1003 -0
  378. package/scripts/maintenance/step-guard.mjs +74 -0
  379. package/scripts/maintenance/update-core.mjs +14 -831
  380. package/scripts/utils/console-guard.mjs +13 -0
  381. package/scripts/utils/generated-tag.mjs +66 -0
  382. package/scripts/utils/index.mjs +9 -1
  383. package/scripts/utils/logging.mjs +140 -0
  384. package/templates/app/(auth)/accept-invite/[token]/page.tsx +4 -3
  385. package/templates/app/(auth)/verify-email/page.tsx +4 -3
  386. package/templates/app/(public)/docs/[section]/[page]/page.tsx +11 -0
  387. package/templates/app/api/auth/[...all]/route.ts +15 -14
  388. package/templates/app/api/internal/user-metadata/route.ts +6 -8
  389. package/templates/app/api/user/plan-flags/route.ts +1 -1
  390. package/templates/app/api/user/profile/route.ts +12 -17
  391. package/templates/app/api/v1/api-keys/route.ts +1 -1
  392. package/templates/app/api/v1/auth/signup-with-invite/route.ts +2 -1
  393. package/templates/app/api/v1/billing/cancel/route.ts +1 -1
  394. package/templates/app/api/v1/billing/change-plan/route.ts +1 -1
  395. package/templates/app/api/v1/billing/check-action/route.ts +1 -1
  396. package/templates/app/api/v1/billing/checkout/route.ts +1 -1
  397. package/templates/app/api/v1/blocks/validate/route.ts +1 -1
  398. package/templates/app/api/v1/media/[id]/tags/route.ts +1 -1
  399. package/templates/app/api/v1/post-categories/[id]/route.ts +1 -1
  400. package/templates/app/api/v1/post-categories/route.ts +1 -1
  401. package/templates/app/api/v1/teams/[teamId]/members/route.ts +2 -1
  402. package/templates/app/api/v1/teams/[teamId]/subscription/route.ts +3 -5
  403. package/templates/app/api/v1/teams/docs.md +4 -9
  404. package/templates/app/api/v1/teams/switch/route.ts +1 -1
  405. package/templates/app/api/v1/users/[id]/meta/[key]/route.ts +1 -1
  406. package/templates/app/api/v1/users/[id]/route.ts +1 -1
  407. package/templates/app/api/v1/users/route.ts +1 -1
  408. package/templates/app/dashboard/(main)/media/page.tsx +3 -1
  409. package/templates/app/dashboard/settings/api-keys/page.tsx +5 -4
  410. package/templates/app/dashboard/settings/profile/page.tsx +3 -2
  411. package/templates/app/devtools/scheduled-actions/page.tsx +2 -1
  412. package/templates/app/superadmin/docs/[section]/[page]/page.tsx +16 -0
  413. package/templates/app/superadmin/subscriptions/page.tsx +3 -2
  414. package/templates/app/superadmin/teams/[teamId]/page.tsx +5 -4
  415. package/templates/app/superadmin/users/[userId]/page.tsx +2 -1
  416. package/templates/app/superadmin/users/page.tsx +2 -1
  417. package/templates/blocks/cta-section/component.tsx +3 -2
  418. package/templates/blocks/cta-section/schema.ts +1 -1
  419. package/templates/blocks/features-grid/component.tsx +2 -1
  420. package/templates/blocks/features-grid/schema.ts +1 -1
  421. package/templates/blocks/hero/component.tsx +3 -2
  422. package/templates/blocks/hero/schema.ts +1 -1
  423. package/templates/blocks/testimonials/component.tsx +2 -1
  424. package/templates/blocks/testimonials/schema.ts +1 -1
  425. package/templates/blocks/text-content/component.tsx +2 -1
  426. package/templates/blocks/text-content/schema.ts +1 -1
  427. package/templates/ci-workflows/README.md +1 -1
  428. package/templates/ci-workflows/github/cypress-regression.yml +1 -1
  429. package/templates/ci-workflows/github/cypress-smoke.yml +1 -1
  430. package/templates/contents/plugins/starter/plugin/api/example/route.ts.template +1 -1
  431. package/templates/contents/themes/starter/blocks/cta-section/component.tsx +3 -2
  432. package/templates/contents/themes/starter/blocks/cta-section/schema.ts +1 -1
  433. package/templates/contents/themes/starter/blocks/features-grid/component.tsx +2 -1
  434. package/templates/contents/themes/starter/blocks/features-grid/schema.ts +1 -1
  435. package/templates/contents/themes/starter/blocks/hero/component.tsx +3 -2
  436. package/templates/contents/themes/starter/blocks/hero/schema.ts +1 -1
  437. package/templates/contents/themes/starter/blocks/testimonials/component.tsx +3 -2
  438. package/templates/contents/themes/starter/blocks/testimonials/schema.ts +1 -1
  439. package/templates/contents/themes/starter/blocks/text-content/component.tsx +2 -1
  440. package/templates/contents/themes/starter/blocks/text-content/schema.ts +1 -1
  441. package/templates/contents/themes/starter/entities/posts/components/post-header.tsx +2 -1
  442. package/templates/contents/themes/starter/lib/hooks/useAnalyticsData.ts +2 -1
  443. package/templates/contents/themes/starter/lib/hooks/useTaskStats.ts +2 -1
  444. package/templates/eslint.config.mjs +133 -9
  445. package/templates/features/blog/blocks/post-content/component.tsx +2 -1
  446. package/templates/features/blog/blocks/post-content/schema.ts +1 -1
  447. package/templates/features/blog/entities/posts/components/post-header.tsx +2 -1
  448. package/templates/features/pages/blocks/hero/component.tsx +3 -2
  449. package/templates/features/pages/blocks/hero/schema.ts +1 -1
  450. package/templates/next.config.mjs +50 -29
  451. package/templates/proxy.ts +100 -21
  452. package/templates/public/sw.js +2 -2
  453. package/templates/scripts/cy-run-prod.cjs +5 -5
  454. package/tests/jest/__mocks__/@nextsparkjs/registries/template-registry.client.ts +11 -0
  455. package/tests/jest/__mocks__/next-server.js +10 -0
  456. package/scripts/build/docs.mjs +0 -392
@@ -1,10 +1,10 @@
1
1
  /**
2
2
  * Tests for icon discovery.
3
3
  *
4
- * The two halves that can silently produce an empty or wrong registry — which
4
+ * The parts that can silently produce an empty or wrong registry — which
5
5
  * degrades to every icon rendering as a Box, with no error anywhere — are the
6
- * path matcher and the name extraction. Both are pure, so both are tested here
7
- * directly; discoverIcons() itself only wires them to the filesystem.
6
+ * path matchers and the name extraction. Both are pure, so both are tested
7
+ * here directly; discoverIcons() itself only wires them to the filesystem.
8
8
  *
9
9
  * Run: node --test packages/core/scripts/build/registry/__tests__/icons-discovery.test.mjs
10
10
  */
@@ -12,7 +12,20 @@
12
12
  import { test } from 'node:test'
13
13
  import assert from 'node:assert/strict'
14
14
 
15
- import { findUnresolvedIconRefs, extractIconNames, isIconSourcePath } from '../discovery/icons.mjs'
15
+ import {
16
+ findUnresolvedIconRefs,
17
+ extractIconNames,
18
+ isIconSourcePath,
19
+ isIconCallSourcePath,
20
+ isTestFilePath,
21
+ extractLiteralIconCallNames
22
+ } from '../discovery/icons.mjs'
23
+
24
+ // extractLiteralIconCallNames matches a call site by its real import binding,
25
+ // so every snippet exercising a real DynamicIcon or resolveIcon call needs
26
+ // one of these in scope, the same way real theme and plugin source does.
27
+ const DYNAMIC_ICON_IMPORT = "import { DynamicIcon } from '@nextsparkjs/core/components/ui/dynamic-icon'\n"
28
+ const RESOLVE_ICON_IMPORT = "import { resolveIcon } from '@nextsparkjs/core/lib/icons'\n"
16
29
 
17
30
  test('matches entity, block and app configs', () => {
18
31
  assert.ok(isIconSourcePath('/p/contents/themes/default/entities/tasks/tasks.config.ts'))
@@ -26,9 +39,19 @@ test('matches the same paths with Windows separators', () => {
26
39
  assert.ok(isIconSourcePath('C:\\p\\themes\\default\\config\\app.config.ts'))
27
40
  })
28
41
 
29
- test('ignores configs whose icons never reach resolveIcon', () => {
30
- assert.equal(isIconSourcePath('/p/themes/crm/config/features.config.ts'), false)
31
- assert.equal(isIconSourcePath('/p/themes/default/config/theme.config.ts'), false)
42
+ test('matches every config/*.config.ts of a theme or plugin, not only app.config.ts', () => {
43
+ assert.ok(isIconSourcePath('/p/themes/crm/config/dashboard.config.ts'))
44
+ assert.ok(isIconSourcePath('/p/themes/crm/config/features.config.ts'))
45
+ assert.ok(isIconSourcePath('/p/themes/crm/config/flows.config.ts'))
46
+ assert.ok(isIconSourcePath('/p/themes/default/config/theme.config.ts'))
47
+ assert.ok(isIconSourcePath('C:\\p\\themes\\crm\\config\\features.config.ts'))
48
+ })
49
+
50
+ test('ignores a config.ts nested deeper than a direct child of config/', () => {
51
+ assert.equal(isIconSourcePath('/p/themes/default/config/sub/nested.config.ts'), false)
52
+ })
53
+
54
+ test('ignores files outside entities, blocks and config directories', () => {
32
55
  assert.equal(isIconSourcePath('/p/themes/default/entities/tasks/messages/en.ts'), false)
33
56
  })
34
57
 
@@ -36,53 +59,140 @@ test('does not match a file that merely ends in config.ts', () => {
36
59
  assert.equal(isIconSourcePath('/p/themes/default/blocks/hero/notconfig.ts'), false)
37
60
  })
38
61
 
39
- test('reads an icon under a quoted key', () => {
62
+ // --- isTestFilePath: test code is excluded from discovery entirely --------
63
+
64
+ test('flags .test. and .spec. files, ts and tsx', () => {
65
+ assert.ok(isTestFilePath('/p/themes/default/components/wallet-badge.test.tsx'))
66
+ assert.ok(isTestFilePath('/p/themes/default/components/wallet-badge.spec.ts'))
67
+ })
68
+
69
+ test('flags anything under __tests__, tests or cypress directories', () => {
70
+ assert.ok(isTestFilePath('/p/themes/default/__tests__/wallet-badge.tsx'))
71
+ assert.ok(isTestFilePath('/p/themes/default/tests/wallet-badge.tsx'))
72
+ assert.ok(isTestFilePath('/p/themes/default/tests/cypress/e2e/wallet.cy.ts'))
73
+ assert.ok(isTestFilePath('C:\\p\\themes\\default\\__tests__\\wallet-badge.tsx'))
74
+ })
75
+
76
+ test('does not flag ordinary component source', () => {
77
+ assert.equal(isTestFilePath('/p/themes/default/components/wallet-badge.tsx'), false)
78
+ assert.equal(isTestFilePath('/p/themes/default/entities/tasks/tasks.config.ts'), false)
79
+ })
80
+
81
+ test('reads an icon under a quoted key', async () => {
40
82
  const source = `
41
83
  import { Users } from 'lucide-react'
42
84
  export const config = { 'icon': Users }
43
85
  `
44
- assert.deepEqual(extractIconNames(source), ['Users'])
86
+ assert.deepEqual(await extractIconNames(source), ['Users'])
45
87
  })
46
88
 
47
- test('reads an icon declared as a lucide import', () => {
89
+ test('reads an icon declared as a lucide import', async () => {
48
90
  const source = `
49
91
  import { Users, CheckSquare } from 'lucide-react'
50
92
  export const config = { icon: CheckSquare }
51
93
  `
52
- assert.deepEqual(extractIconNames(source), ['CheckSquare'])
94
+ assert.deepEqual(await extractIconNames(source), ['CheckSquare'])
53
95
  })
54
96
 
55
- test('resolves an aliased import back to the lucide export name', () => {
97
+ test('resolves an aliased import back to the lucide export name', async () => {
56
98
  // `icon: HouseIcon` is lucide's `Home`; the registry is keyed by the export
57
99
  const source = `
58
100
  import { Home as HouseIcon } from 'lucide-react'
59
101
  export const config = { icon: HouseIcon }
60
102
  `
61
- assert.deepEqual(extractIconNames(source), ['Home'])
103
+ assert.deepEqual(await extractIconNames(source), ['Home'])
62
104
  })
63
105
 
64
- test('ignores an identifier that did not come from lucide', () => {
106
+ test('ignores an identifier that did not come from lucide', async () => {
65
107
  const source = `
66
108
  import { MyOwnIcon } from '@/components/icons'
67
109
  export const config = { icon: MyOwnIcon }
68
110
  `
69
- assert.deepEqual(extractIconNames(source), [])
111
+ assert.deepEqual(await extractIconNames(source), [])
70
112
  })
71
113
 
72
- test('reads a string icon name, kebab-case included', () => {
114
+ test('reads a string icon name, kebab-case included', async () => {
73
115
  const source = `export const config = { icon: 'pie-chart' }`
74
- assert.deepEqual(extractIconNames(source), ['pie-chart'])
116
+ assert.deepEqual(await extractIconNames(source), ['pie-chart'])
75
117
  })
76
118
 
77
- test('does not match a different key that ends in icon', () => {
119
+ test('does not match a different key that ends in icon', async () => {
78
120
  const source = `
79
121
  import { Users } from 'lucide-react'
80
122
  export const config = { sidebarIcon: Users, my_icon: Users }
81
123
  `
82
- assert.deepEqual(extractIconNames(source), [])
124
+ assert.deepEqual(await extractIconNames(source), [])
125
+ })
126
+
127
+ test('reads an icon from a shorthand property, where the name doubles as the value', async () => {
128
+ const source = `
129
+ import { Wallet as icon } from 'lucide-react'
130
+ export default { icon }
131
+ `
132
+ assert.deepEqual(await extractIconNames(source), ['Wallet'])
133
+ })
134
+
135
+ test('reads an icon written under a computed property key', async () => {
136
+ const source = `export default { ['icon']: 'Wallet' }`
137
+ assert.deepEqual(await extractIconNames(source), ['Wallet'])
138
+ })
139
+
140
+ test('reads an icon under the iconName key, not only icon', async () => {
141
+ const source = `export default { iconName: 'Wallet' }`
142
+ assert.deepEqual(await extractIconNames(source), ['Wallet'])
143
+ })
144
+
145
+ test('reports a shorthand icon property that does not resolve, instead of staying silent', async () => {
146
+ assert.deepEqual(
147
+ await findUnresolvedIconRefs("import { icon } from './local-icons'\nexport default { icon }"),
148
+ ['icon']
149
+ )
83
150
  })
84
151
 
85
- test('never yields anything but a bare name, so nothing can be injected', () => {
152
+ test('reads an icon written as a get accessor, the same as a plain property', async () => {
153
+ const source = `export default { get icon() { return 'Wallet' } }`
154
+ assert.deepEqual(await extractIconNames(source), ['Wallet'])
155
+ assert.deepEqual(await findUnresolvedIconRefs(source), [])
156
+ })
157
+
158
+ test('reads a get accessor icon returning a lucide import', async () => {
159
+ const source = `
160
+ import { Wallet } from 'lucide-react'
161
+ export default { get icon() { return Wallet } }
162
+ `
163
+ assert.deepEqual(await extractIconNames(source), ['Wallet'])
164
+ })
165
+
166
+ test('reports a get accessor icon whose return value cannot be read statically, instead of staying silent', async () => {
167
+ const source = `export default { get icon() {\n const computed = someHelper()\n return computed\n} }`
168
+ assert.deepEqual(await extractIconNames(source), [])
169
+ assert.equal((await findUnresolvedIconRefs(source)).length, 1)
170
+ })
171
+
172
+ test('reports an icon written as a method, whose value is the function rather than a name', async () => {
173
+ const source = `export default { icon() { return 'Wallet' } }`
174
+ assert.deepEqual(await extractIconNames(source), [])
175
+ assert.deepEqual(await findUnresolvedIconRefs(source), ["icon() { return 'Wallet' }"])
176
+ })
177
+
178
+ test('reports a set accessor icon with no getter beside it, whose value reads as undefined', async () => {
179
+ const source = `export default { set icon(value) {} }`
180
+ assert.deepEqual(await extractIconNames(source), [])
181
+ assert.deepEqual(await findUnresolvedIconRefs(source), ['set icon(value) {}'])
182
+ })
183
+
184
+ test('says nothing about a set accessor icon when a getter beside it supplies the name', async () => {
185
+ const source = `export default { get icon() { return 'Wallet' }, set icon(value) {} }`
186
+ assert.deepEqual(await extractIconNames(source), ['Wallet'])
187
+ assert.deepEqual(await findUnresolvedIconRefs(source), [])
188
+ })
189
+
190
+ test('reads an icon written as a class field, and reports one that does not resolve', async () => {
191
+ assert.deepEqual(await extractIconNames(`export class Config { icon = 'Wallet' }`), ['Wallet'])
192
+ assert.deepEqual(await findUnresolvedIconRefs(`export class Config { icon = pickIcon() }`), ['pickIcon()'])
193
+ })
194
+
195
+ test('never yields anything but a bare name, so nothing can be injected', async () => {
86
196
  // The generated registry is TypeScript built from these strings, and every
87
197
  // name becomes a named import. Whatever a config holds, what comes out here
88
198
  // is a bare name — and discoverIcons then drops any that lucide doesn't
@@ -94,7 +204,7 @@ test('never yields anything but a bare name, so nothing can be injected', () =>
94
204
  ]
95
205
 
96
206
  for (const source of sources) {
97
- for (const name of extractIconNames(source)) {
207
+ for (const name of await extractIconNames(source)) {
98
208
  assert.match(name, /^[A-Za-z][\w$-]*$/, `unsafe name from: ${source}`)
99
209
  }
100
210
  }
@@ -102,29 +212,420 @@ test('never yields anything but a bare name, so nothing can be injected', () =>
102
212
 
103
213
  // Only a direct lucide import or a string literal reaches the registry, so
104
214
  // every other shape has to be reported rather than silently rendering a Box.
105
- test('reports an icon reference the build cannot resolve', () => {
215
+ test('reports an icon reference the build cannot resolve', async () => {
106
216
  assert.deepEqual(
107
- findUnresolvedIconRefs("import * as I from 'lucide-react'\nexport default { icon: I.Users }"),
217
+ await findUnresolvedIconRefs("import * as I from 'lucide-react'\nexport default { icon: I.Users }"),
108
218
  ['I.Users']
109
219
  )
110
220
  assert.deepEqual(
111
- findUnresolvedIconRefs("import { Users } from './icons'\nexport default { icon: Users }"),
221
+ await findUnresolvedIconRefs("import { Users } from './icons'\nexport default { icon: Users }"),
112
222
  ['Users']
113
223
  )
114
224
  assert.deepEqual(
115
- findUnresolvedIconRefs("import { Users } from 'lucide-react'\nconst Mine = Users\nexport default { icon: Mine }"),
225
+ await findUnresolvedIconRefs("import { Users } from 'lucide-react'\nconst Mine = Users\nexport default { icon: Mine }"),
116
226
  ['Mine']
117
227
  )
118
228
  })
119
229
 
120
- test('says nothing about a reference it can resolve', () => {
230
+ test('says nothing about a reference it can resolve', async () => {
121
231
  assert.deepEqual(
122
- findUnresolvedIconRefs("import { Users } from 'lucide-react'\nexport default { icon: Users }"),
232
+ await findUnresolvedIconRefs("import { Users } from 'lucide-react'\nexport default { icon: Users }"),
123
233
  []
124
234
  )
125
235
  assert.deepEqual(
126
- findUnresolvedIconRefs("import { Home as HouseIcon } from 'lucide-react'\nexport default { icon: HouseIcon }"),
236
+ await findUnresolvedIconRefs("import { Home as HouseIcon } from 'lucide-react'\nexport default { icon: HouseIcon }"),
127
237
  []
128
238
  )
129
- assert.deepEqual(findUnresolvedIconRefs("export default { icon: 'pie-chart' }"), [])
239
+ assert.deepEqual(await findUnresolvedIconRefs("export default { icon: 'pie-chart' }"), [])
240
+ })
241
+
242
+ // --- Transparent TypeScript wrappers ---------------------------------------
243
+ //
244
+ // `as`, `<T>x`, `satisfies` and `!` change nothing about the value at
245
+ // runtime, so a config or call site written with one of them must resolve
246
+ // exactly like the bare form — and still be reported as unresolved when the
247
+ // wrapped value itself doesn't resolve, rather than disappearing silently.
248
+
249
+ test('reads a string icon name through `as const`', async () => {
250
+ const source = `export const config = { icon: 'Wallet' as const }`
251
+ assert.deepEqual(await extractIconNames(source), ['Wallet'])
252
+ })
253
+
254
+ test('reads a lucide identifier through a non-null assertion', async () => {
255
+ const source = `
256
+ import { Wallet } from 'lucide-react'
257
+ export const config = { icon: Wallet! }
258
+ `
259
+ assert.deepEqual(await extractIconNames(source), ['Wallet'])
260
+ })
261
+
262
+ test('reads a string icon name through `satisfies` and through parentheses', async () => {
263
+ assert.deepEqual(
264
+ await extractIconNames(`export const config = { icon: 'Wallet' satisfies string }`),
265
+ ['Wallet']
266
+ )
267
+ assert.deepEqual(await extractIconNames(`export const config = { icon: ('Wallet') }`), ['Wallet'])
268
+ })
269
+
270
+ test('reads a lucide identifier through an angle-bracket type assertion', async () => {
271
+ const source = `
272
+ import { Wallet } from 'lucide-react'
273
+ export const config = { icon: <any>Wallet }
274
+ `
275
+ assert.deepEqual(await extractIconNames(source), ['Wallet'])
276
+ })
277
+
278
+ test('reports a wrapped reference that still does not resolve, instead of dropping it silently', async () => {
279
+ assert.deepEqual(
280
+ await findUnresolvedIconRefs("export default { icon: Wallet! }"),
281
+ ['Wallet!']
282
+ )
283
+ assert.deepEqual(
284
+ await findUnresolvedIconRefs("export default { icon: Wallet as const }"),
285
+ ['Wallet as const']
286
+ )
287
+ })
288
+
289
+ test('reports a call result assigned to icon, instead of staying silent', async () => {
290
+ // Neither a string literal nor a lucide identifier - `chooseIcon()` cannot
291
+ // be resolved at build time, and unlike a bare identifier or a property
292
+ // access it must still be reported, not just left out of both lists.
293
+ assert.deepEqual(await extractIconNames('export default { icon: chooseIcon() as LucideIcon }'), [])
294
+ assert.deepEqual(
295
+ await findUnresolvedIconRefs('export default { icon: chooseIcon() as LucideIcon }'),
296
+ ['chooseIcon() as LucideIcon']
297
+ )
298
+ })
299
+
300
+ test('extracts a literal name through `satisfies` in a call argument', async () => {
301
+ const source = `${RESOLVE_ICON_IMPORT}resolveIcon('Wallet' satisfies string)`
302
+ assert.deepEqual(await extractLiteralIconCallNames(source), ['Wallet'])
303
+ })
304
+
305
+ test('extracts a literal name through a non-null-asserted callee', async () => {
306
+ const source = `${RESOLVE_ICON_IMPORT}resolveIcon!('Wallet')`
307
+ assert.deepEqual(await extractLiteralIconCallNames(source), ['Wallet'])
308
+ })
309
+
310
+ test('matches .ts and .tsx component files, with Windows separators too', () => {
311
+ assert.ok(isIconCallSourcePath('/p/themes/default/components/wallet-badge.tsx'))
312
+ assert.ok(isIconCallSourcePath('/p/themes/default/lib/format.ts'))
313
+ assert.ok(isIconCallSourcePath('C:\\p\\themes\\default\\components\\wallet-badge.tsx'))
314
+ assert.equal(isIconCallSourcePath('/p/themes/default/messages/en.json'), false)
315
+ assert.equal(isIconCallSourcePath('/p/themes/default/README.md'), false)
316
+ })
317
+
318
+ test('extracts a literal name from DynamicIcon, either quoting style', async () => {
319
+ assert.deepEqual(
320
+ await extractLiteralIconCallNames(DYNAMIC_ICON_IMPORT + '<DynamicIcon name="Wallet" className="h-4 w-4" />'),
321
+ ['Wallet']
322
+ )
323
+ assert.deepEqual(
324
+ await extractLiteralIconCallNames(DYNAMIC_ICON_IMPORT + "<DynamicIcon name={'pie-chart'} />"),
325
+ ['pie-chart']
326
+ )
327
+ })
328
+
329
+ test('extracts a literal name from resolveIcon, with or without a fallback argument', async () => {
330
+ assert.deepEqual(await extractLiteralIconCallNames(RESOLVE_ICON_IMPORT + "resolveIcon('receipt')"), ['receipt'])
331
+ assert.deepEqual(
332
+ await extractLiteralIconCallNames(RESOLVE_ICON_IMPORT + 'resolveIcon("receipt", Box)'),
333
+ ['receipt']
334
+ )
335
+ })
336
+
337
+ test('ignores a runtime value handed to DynamicIcon or resolveIcon', async () => {
338
+ assert.deepEqual(await extractLiteralIconCallNames(DYNAMIC_ICON_IMPORT + '<DynamicIcon name={item.icon} />'), [])
339
+ assert.deepEqual(await extractLiteralIconCallNames(RESOLVE_ICON_IMPORT + 'resolveIcon(item.icon, Box)'), [])
340
+ assert.deepEqual(await extractLiteralIconCallNames(RESOLVE_ICON_IMPORT + 'resolveIcon(iconName)'), [])
341
+ })
342
+
343
+ test('never yields anything but a bare name from a call, so nothing can be injected', async () => {
344
+ const sources = [
345
+ DYNAMIC_ICON_IMPORT + '<DynamicIcon name="X&quot;; process.exit(1); //" />',
346
+ RESOLVE_ICON_IMPORT + 'resolveIcon(\'Users } from "x"; import evil from "y"; //\')'
347
+ ]
348
+
349
+ for (const source of sources) {
350
+ for (const name of await extractLiteralIconCallNames(source)) {
351
+ assert.match(name, /^[A-Za-z][\w$-]*$/, `unsafe name from: ${source}`)
352
+ }
353
+ }
354
+ })
355
+
356
+ // --- Import-binding resolution --------------------------------------------
357
+ //
358
+ // A call site is matched through the file's own imports and lexical scope
359
+ // rather than by comparing the identifier's text, so aliasing and namespacing
360
+ // resolve, a same-named local that never came from core does not, and a
361
+ // closer declaration of the same name shadows the import for its scope.
362
+
363
+ test('resolves resolveIcon through an aliased import', async () => {
364
+ const source = "import { resolveIcon as ri } from '@nextsparkjs/core/lib/icons'\nri('Wallet')"
365
+ assert.deepEqual(await extractLiteralIconCallNames(source), ['Wallet'])
366
+ })
367
+
368
+ test('resolves DynamicIcon through an aliased import', async () => {
369
+ const source = "import { DynamicIcon as DI } from '@nextsparkjs/core/components/ui/dynamic-icon'\n<DI name=\"Wallet\" />"
370
+ assert.deepEqual(await extractLiteralIconCallNames(source), ['Wallet'])
371
+ })
372
+
373
+ test('resolves resolveIcon and DynamicIcon through a namespace import', async () => {
374
+ const source =
375
+ "import * as Core from '@nextsparkjs/core/lib/icons'\nimport * as CoreUI from '@nextsparkjs/core/components/ui/dynamic-icon'\nCore.resolveIcon('Wallet');\n<CoreUI.DynamicIcon name=\"Receipt\" />;"
376
+ assert.deepEqual(await extractLiteralIconCallNames(source), ['Wallet', 'Receipt'])
377
+ })
378
+
379
+ test('ignores a same-named local that was never imported from core', async () => {
380
+ // A theme's own helper named resolveIcon (or a JSX component named
381
+ // DynamicIcon) is not core's, even though the text matches.
382
+ const source = "function resolveIcon(x) { return x }\nresolveIcon('Wallet')\nfunction DynamicIcon(props) { return null }\n<DynamicIcon name=\"Receipt\" />"
383
+ assert.deepEqual(await extractLiteralIconCallNames(source), [])
384
+ })
385
+
386
+ test('ignores resolveIcon imported from somewhere other than core', async () => {
387
+ const source = "import { resolveIcon } from './local-icons'\nresolveIcon('Wallet')"
388
+ assert.deepEqual(await extractLiteralIconCallNames(source), [])
389
+ })
390
+
391
+ test('ignores a call through a parameter that shadows the real core import', async () => {
392
+ // The module does import resolveIcon from core, but the call inside f()
393
+ // goes through f's own parameter of the same name, not the import.
394
+ const source = `${RESOLVE_ICON_IMPORT}function f(resolveIcon) { resolveIcon('Wallet') }`
395
+ assert.deepEqual(await extractLiteralIconCallNames(source), [])
396
+ })
397
+
398
+ test('still resolves the real import once the shadowing parameter goes out of scope', async () => {
399
+ const source = `${RESOLVE_ICON_IMPORT}function f(resolveIcon) { resolveIcon('Wallet') }\nresolveIcon('Receipt')`
400
+ assert.deepEqual(await extractLiteralIconCallNames(source), ['Receipt'])
401
+ })
402
+
403
+ // --- Function parameter initializer scope ----------------------------------
404
+ //
405
+ // Parameter defaults run before a function body is entered. Parameters and a
406
+ // named function expression shadow imports there, but body declarations do
407
+ // not; those declarations still shadow calls made by the body itself.
408
+
409
+ test('discovers a constructor default that a var in the constructor body does not shadow', async () => {
410
+ const source = `${RESOLVE_ICON_IMPORT}export class Probe {\n constructor(icon = resolveIcon('Telescope')) {\n var resolveIcon = () => null\n }\n}`
411
+ assert.deepEqual(await extractLiteralIconCallNames(source), ['Telescope'])
412
+ })
413
+
414
+ test('discovers a constructor default despite a function declaration in its body', async () => {
415
+ const source = `${RESOLVE_ICON_IMPORT}export class Probe {\n constructor(icon = resolveIcon('Satellite')) {\n function resolveIcon() {}\n }\n}`
416
+ assert.deepEqual(await extractLiteralIconCallNames(source), ['Satellite'])
417
+ })
418
+
419
+ test('discovers a method default despite a function declaration in its body', async () => {
420
+ const source = `${RESOLVE_ICON_IMPORT}export class Probe {\n render(icon = resolveIcon('Map')) {\n function resolveIcon() {}\n }\n}`
421
+ assert.deepEqual(await extractLiteralIconCallNames(source), ['Map'])
422
+ })
423
+
424
+ test('discovers an arrow default despite a function declaration in its block body', async () => {
425
+ const source = `${RESOLVE_ICON_IMPORT}const render = (icon = resolveIcon('Rocket')) => {\n function resolveIcon() {}\n}`
426
+ assert.deepEqual(await extractLiteralIconCallNames(source), ['Rocket'])
427
+ })
428
+
429
+ test('discovers a destructured parameter default that a body var does not shadow', async () => {
430
+ const source = `${RESOLVE_ICON_IMPORT}const render = ({ icon = resolveIcon('Compass') } = {}) => {\n var resolveIcon = () => null\n}`
431
+ assert.deepEqual(await extractLiteralIconCallNames(source), ['Compass'])
432
+ })
433
+
434
+ test('discovers a computed destructured parameter key that a body var does not shadow', async () => {
435
+ const source = `${RESOLVE_ICON_IMPORT}const render = ({ [resolveIcon('Globe')]: icon } = {}) => {\n var resolveIcon = () => null\n}`
436
+ assert.deepEqual(await extractLiteralIconCallNames(source), ['Globe'])
437
+ })
438
+
439
+ test('keeps a body var shadowing calls made by the function body', async () => {
440
+ const source = `${RESOLVE_ICON_IMPORT}export class Probe {\n constructor(a = 1) {\n var resolveIcon = f\n resolveIcon('Anchor')\n }\n}`
441
+ assert.deepEqual(await extractLiteralIconCallNames(source), [])
442
+ })
443
+
444
+ test("discovers a method's computed name, which its own parameters do not shadow", async () => {
445
+ const source = `${RESOLVE_ICON_IMPORT}export class Probe {\n [resolveIcon('Flag')](resolveIcon) {\n var resolveIcon\n }\n}`
446
+ assert.deepEqual(await extractLiteralIconCallNames(source), ['Flag'])
447
+ })
448
+
449
+ test('keeps an earlier parameter shadowing a later parameter default', async () => {
450
+ const source = `${RESOLVE_ICON_IMPORT}const render = (resolveIcon, icon = resolveIcon('Kite')) => {}`
451
+ assert.deepEqual(await extractLiteralIconCallNames(source), [])
452
+ })
453
+
454
+ test('ignores a call imported from a same-prefixed package that is not core', async () => {
455
+ const source = "import { resolveIcon } from '@nextsparkjs/core-fake/lib/icons'\nresolveIcon('Wallet')"
456
+ assert.deepEqual(await extractLiteralIconCallNames(source), [])
457
+ })
458
+
459
+ // --- Block-wide shadowing ---------------------------------------------------
460
+ //
461
+ // A local declaration shadows an import for the rest of the block it is
462
+ // declared in, not merely for its own immediate subtree: a sibling statement
463
+ // later in the same block has to see the shadow too, the way it would at
464
+ // runtime.
465
+
466
+ test('ignores a call through a local variable that shadows the import later in the same block', async () => {
467
+ const source = `${RESOLVE_ICON_IMPORT}function f(local) {\n const resolveIcon = local\n resolveIcon('Wallet')\n}`
468
+ assert.deepEqual(await extractLiteralIconCallNames(source), [])
469
+ })
470
+
471
+ test('ignores a call through a local function declaration that shadows the import', async () => {
472
+ const source = `${RESOLVE_ICON_IMPORT}function outer() {\n function resolveIcon(x) { return x }\n resolveIcon('Wallet')\n}`
473
+ assert.deepEqual(await extractLiteralIconCallNames(source), [])
474
+ })
475
+
476
+ test('ignores a call inside a named function expression that shadows the import with its own name', async () => {
477
+ const source = `${RESOLVE_ICON_IMPORT}const wrapped = function resolveIcon(x) {\n resolveIcon('Wallet')\n return x\n}`
478
+ assert.deepEqual(await extractLiteralIconCallNames(source), [])
479
+ })
480
+
481
+ test('ignores a call through a local namespace that shadows the imported namespace', async () => {
482
+ const source = "import * as Core from '@nextsparkjs/core/lib/icons'\nfunction f() {\n const Core = { resolveIcon: () => {} }\n Core.resolveIcon('Wallet')\n}"
483
+ assert.deepEqual(await extractLiteralIconCallNames(source), [])
484
+ })
485
+
486
+ test('ignores a call through a var that shadows the import, hoisted out of the if that declares it', async () => {
487
+ // var is function-scoped, not block-scoped: the call after the if still
488
+ // sees the local declaration, not the import.
489
+ const source = `${RESOLVE_ICON_IMPORT}function f() {\n if (true) {\n var resolveIcon = (name) => name\n }\n return resolveIcon('Wallet')\n}`
490
+ assert.deepEqual(await extractLiteralIconCallNames(source), [])
491
+ })
492
+
493
+ test("ignores a call through a for loop's own initializer that shadows the import for the whole loop body", async () => {
494
+ const source = `${RESOLVE_ICON_IMPORT}function f() {\n for (const resolveIcon = (name) => name; false; ) {\n resolveIcon('Wallet')\n }\n}`
495
+ assert.deepEqual(await extractLiteralIconCallNames(source), [])
496
+ })
497
+
498
+ test('ignores a call through a let declared in one switch case that shadows the import in a later case', async () => {
499
+ // Every clause of a switch shares one lexical scope, unlike a block per case.
500
+ const source = `${RESOLVE_ICON_IMPORT}function f(x) {\n switch (x) {\n case 1:\n let resolveIcon = (name) => name\n break\n case 2:\n resolveIcon('Wallet')\n break\n }\n}`
501
+ assert.deepEqual(await extractLiteralIconCallNames(source), [])
502
+ })
503
+
504
+ test('ignores a call through a class declaration that shadows the import with its own name', async () => {
505
+ const source = `${RESOLVE_ICON_IMPORT}function f() {\n class resolveIcon {\n static call(name) { return name }\n }\n return resolveIcon('Wallet')\n}`
506
+ assert.deepEqual(await extractLiteralIconCallNames(source), [])
507
+ })
508
+
509
+ // --- Accessor, constructor, class and namespace scopes ----------------------
510
+ //
511
+ // Getters, setters and constructors are functions like any other: their
512
+ // parameters and every var hoisted in their bodies shadow the import inside
513
+ // them and nowhere else. A named class expression binds its own name only
514
+ // inside its body. A class `static { }` block and a TypeScript namespace are
515
+ // var scopes of their own, so a var declared in one neither leaks out to
516
+ // shadow the module's real import nor stays unseen inside.
517
+
518
+ test('ignores a call through a var hoisted out of an if inside a getter', async () => {
519
+ const source = `${RESOLVE_ICON_IMPORT}export const menu = {\n get icon() {\n if (true) {\n var resolveIcon = (name) => name\n }\n return resolveIcon('Wallet')\n }\n}`
520
+ assert.deepEqual(await extractLiteralIconCallNames(source), [])
521
+ })
522
+
523
+ test('ignores a call through a setter parameter that shadows the import', async () => {
524
+ const source = `${RESOLVE_ICON_IMPORT}export const menu = {\n set icon(resolveIcon) {\n resolveIcon('Wallet')\n }\n}`
525
+ assert.deepEqual(await extractLiteralIconCallNames(source), [])
526
+ })
527
+
528
+ test('ignores a call through a constructor parameter that shadows the import', async () => {
529
+ const source = `${RESOLVE_ICON_IMPORT}export class Menu {\n constructor(resolveIcon) {\n resolveIcon('Wallet')\n }\n}`
530
+ assert.deepEqual(await extractLiteralIconCallNames(source), [])
531
+ })
532
+
533
+ test("ignores a call through a named class expression's own name", async () => {
534
+ const source = `${RESOLVE_ICON_IMPORT}export const Menu = class resolveIcon {\n static render() {\n return resolveIcon('Wallet')\n }\n}`
535
+ assert.deepEqual(await extractLiteralIconCallNames(source), [])
536
+ })
537
+
538
+ test('ignores a call through a var hoisted inside a getter, setter or constructor body, in a class too', async () => {
539
+ const hoisted = "if (true) { var resolveIcon = (name) => name }\n resolveIcon('Wallet')"
540
+ for (const member of [`get icon() {\n ${hoisted}\n }`, `set icon(value) {\n ${hoisted}\n }`, `constructor() {\n ${hoisted}\n }`]) {
541
+ assert.deepEqual(await extractLiteralIconCallNames(`${RESOLVE_ICON_IMPORT}export class Menu {\n ${member}\n}`), [], member)
542
+ }
543
+ })
544
+
545
+ test('still resolves the real import outside a setter, constructor or class expression that shadowed it', async () => {
546
+ const source = `${RESOLVE_ICON_IMPORT}const a = { set icon(resolveIcon) {} }\nclass B { constructor(resolveIcon) {} }\nconst C = class resolveIcon {}\nresolveIcon('Receipt')`
547
+ assert.deepEqual(await extractLiteralIconCallNames(source), ['Receipt'])
548
+ })
549
+
550
+ test('a var inside a class static block stays in that block, shadowing inside it and not after it', async () => {
551
+ const inside = `${RESOLVE_ICON_IMPORT}class Menu {\n static {\n if (true) { var resolveIcon = (name) => name }\n resolveIcon('Wallet')\n }\n}`
552
+ const after = `${RESOLVE_ICON_IMPORT}class Menu {\n static {\n var resolveIcon = (name) => name\n }\n}\nresolveIcon('Receipt')`
553
+ assert.deepEqual(await extractLiteralIconCallNames(inside), [])
554
+ assert.deepEqual(await extractLiteralIconCallNames(after), ['Receipt'])
555
+ })
556
+
557
+ test('a declaration inside a namespace shadows the import there and nowhere else', async () => {
558
+ const inside = `${RESOLVE_ICON_IMPORT}namespace Menu {\n const resolveIcon = (name: string) => name\n resolveIcon('Wallet')\n}`
559
+ const after = `${RESOLVE_ICON_IMPORT}namespace Menu {\n var resolveIcon = (name: string) => name\n}\nresolveIcon('Receipt')`
560
+ assert.deepEqual(await extractLiteralIconCallNames(inside, 'menu.ts'), [])
561
+ assert.deepEqual(await extractLiteralIconCallNames(after, 'menu.ts'), ['Receipt'])
562
+ })
563
+
564
+ // --- Decoy tests: a name in a non-code position must not reach the registry --
565
+ //
566
+ // A name written inside a comment, a docblock, or only in a test file is not
567
+ // something a real request can ever ask for by string, so discovery must not
568
+ // pick it up just because it looks the same as the code shapes above.
569
+
570
+ test('decoy: a line comment holding `icon: \'Ambulance\'` is not code', async () => {
571
+ const source = `
572
+ // icon: 'Ambulance'
573
+ export const config = {}
574
+ `
575
+ assert.deepEqual(await extractIconNames(source), [])
576
+ })
577
+
578
+ test('decoy: a block comment holding `icon: \'Bird\'` is not code', async () => {
579
+ const source = `
580
+ /* icon: 'Bird' */
581
+ export const config = {}
582
+ `
583
+ assert.deepEqual(await extractIconNames(source), [])
584
+ })
585
+
586
+ test('decoy: a docblock mentioning a lucide identifier is not code', async () => {
587
+ const source = `
588
+ import { Apple } from 'lucide-react'
589
+ /**
590
+ * Renders icon: Apple as an example of a config entry.
591
+ */
592
+ export const config = {}
593
+ `
594
+ assert.deepEqual(await extractIconNames(source), [])
595
+ })
596
+
597
+ test('decoy: a `<DynamicIcon>` inside a line comment is not a call', async () => {
598
+ const source = `${DYNAMIC_ICON_IMPORT}
599
+ // <DynamicIcon name="Cherry" />
600
+ export function Widget() { return null }
601
+ `
602
+ assert.deepEqual(await extractLiteralIconCallNames(source, 'widget.tsx'), [])
603
+ })
604
+
605
+ test('decoy: a `<DynamicIcon>` inside a block comment is not a call', async () => {
606
+ const source = `${DYNAMIC_ICON_IMPORT}
607
+ /* <DynamicIcon name="Bug" /> */
608
+ export function Widget() { return null }
609
+ `
610
+ assert.deepEqual(await extractLiteralIconCallNames(source, 'widget.tsx'), [])
611
+ })
612
+
613
+ test('decoy: a `resolveIcon(...)` inside a block comment is not a call', async () => {
614
+ const source = `${RESOLVE_ICON_IMPORT}
615
+ /* resolveIcon('Fish') */
616
+ export function Widget() { return null }
617
+ `
618
+ assert.deepEqual(await extractLiteralIconCallNames(source, 'widget.tsx'), [])
619
+ })
620
+
621
+ test('decoy: `resolveIcon(...)` in a test file is excluded by path, not by content', async () => {
622
+ // extractLiteralIconCallNames has no notion of "test file" - it reads real
623
+ // code, and `resolveIcon('Citrus')` written directly is real code. The
624
+ // decoy is only harmless because discoverIcons never hands it this file:
625
+ // isIconCallSourcePath combined with isTestFilePath keeps *.test.tsx out of
626
+ // the call-source scan entirely.
627
+ const source = `${RESOLVE_ICON_IMPORT}resolveIcon('Citrus')`
628
+ assert.deepEqual(await extractLiteralIconCallNames(source, 'widget.tsx'), ['Citrus'])
629
+ assert.ok(isIconCallSourcePath('/p/themes/default/components/widget.test.tsx'))
630
+ assert.ok(isTestFilePath('/p/themes/default/components/widget.test.tsx'))
130
631
  })