@jmruthers/pace-core 0.6.1 → 0.6.3

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 (549) hide show
  1. package/CHANGELOG.md +88 -10
  2. package/cursor-rules/00-pace-core-compliance.mdc +46 -87
  3. package/cursor-rules/01-standards-compliance.mdc +16 -47
  4. package/cursor-rules/02-project-structure.mdc +4 -4
  5. package/cursor-rules/03-solid-principles.mdc +45 -164
  6. package/cursor-rules/04-testing-standards.mdc +22 -69
  7. package/cursor-rules/05-bug-reports-and-features.mdc +2 -2
  8. package/cursor-rules/06-code-quality.mdc +42 -125
  9. package/cursor-rules/07-tech-stack-compliance.mdc +33 -128
  10. package/cursor-rules/08-markup-quality.mdc +452 -0
  11. package/cursor-rules/CHANGELOG.md +18 -0
  12. package/cursor-rules/README.md +2 -1
  13. package/dist/{AuthService-DjnJHDtC.d.ts → AuthService-Cb34EQs3.d.ts} +63 -1
  14. package/dist/{DataTable-CH1U5Tpy.d.ts → DataTable-BMRU8a1j.d.ts} +33 -1
  15. package/dist/{DataTable-DQ7RSOHE.js → DataTable-THFPBKTP.js} +12 -10
  16. package/dist/{PublicPageProvider-ce4xlHYA.d.ts → PublicPageProvider-DEMpysFR.d.ts} +394 -171
  17. package/dist/{UnifiedAuthProvider-185Ih4dj.d.ts → UnifiedAuthProvider-CKvHP1MK.d.ts} +30 -8
  18. package/dist/{UnifiedAuthProvider-ATAP5UTR.js → UnifiedAuthProvider-KAGUYQ4J.js} +5 -4
  19. package/dist/{api-N774RPUA.js → api-IAGWF3ZG.js} +10 -10
  20. package/dist/{audit-B5P6FFIR.js → audit-V53FV5AG.js} +2 -2
  21. package/dist/{chunk-JBKQ3SAO.js → chunk-2T2IG7T7.js} +107 -57
  22. package/dist/chunk-2T2IG7T7.js.map +1 -0
  23. package/dist/{chunk-3QRJFVBR.js → chunk-6SOIHG6Z.js} +1 -1
  24. package/dist/chunk-6SOIHG6Z.js.map +1 -0
  25. package/dist/{chunk-3XTALGJF.js → chunk-6Z7LTB3D.js} +69 -240
  26. package/dist/chunk-6Z7LTB3D.js.map +1 -0
  27. package/dist/{chunk-4ZC4GX36.js → chunk-CNCQDFLN.js} +199 -46
  28. package/dist/chunk-CNCQDFLN.js.map +1 -0
  29. package/dist/chunk-DGUM43GV.js +11 -0
  30. package/dist/{chunk-BYFSK72L.js → chunk-DWUBLJJM.js} +361 -187
  31. package/dist/chunk-DWUBLJJM.js.map +1 -0
  32. package/dist/{chunk-LXQLPRQ2.js → chunk-FFQEQTNW.js} +6 -8
  33. package/dist/chunk-FFQEQTNW.js.map +1 -0
  34. package/dist/chunk-FMUCXFII.js +76 -0
  35. package/dist/chunk-FMUCXFII.js.map +1 -0
  36. package/dist/{chunk-4N5C5XZU.js → chunk-HFZBI76P.js} +4 -4
  37. package/dist/chunk-HFZBI76P.js.map +1 -0
  38. package/dist/{chunk-SQGMNID3.js → chunk-L4OXEN46.js} +4 -5
  39. package/dist/chunk-L4OXEN46.js.map +1 -0
  40. package/dist/{chunk-R77UEZ4E.js → chunk-M43Y4SSO.js} +1 -1
  41. package/dist/chunk-M43Y4SSO.js.map +1 -0
  42. package/dist/{chunk-I7PSE6JW.js → chunk-M7MPQISP.js} +3 -76
  43. package/dist/chunk-M7MPQISP.js.map +1 -0
  44. package/dist/chunk-PQBSKX33.js +7793 -0
  45. package/dist/chunk-PQBSKX33.js.map +1 -0
  46. package/dist/chunk-QRPVRXYT.js +226 -0
  47. package/dist/chunk-QRPVRXYT.js.map +1 -0
  48. package/dist/{chunk-KNC55RTG.js → chunk-RWEBCB47.js} +194 -416
  49. package/dist/chunk-RWEBCB47.js.map +1 -0
  50. package/dist/{chunk-XM25TVIE.js → chunk-YDQHOZNA.js} +843 -388
  51. package/dist/chunk-YDQHOZNA.js.map +1 -0
  52. package/dist/{chunk-GLK6VM3F.js → chunk-ZNIWI3UC.js} +739 -737
  53. package/dist/chunk-ZNIWI3UC.js.map +1 -0
  54. package/dist/components.d.ts +5 -5
  55. package/dist/components.js +18 -16
  56. package/dist/components.js.map +1 -1
  57. package/dist/contextValidator-3JNZKUTX.js +9 -0
  58. package/dist/contextValidator-3JNZKUTX.js.map +1 -0
  59. package/dist/eslint-rules/pace-core-compliance.cjs +106 -0
  60. package/dist/{functions-D_kgHktt.d.ts → functions-DHebl8-F.d.ts} +1 -1
  61. package/dist/hooks.d.ts +55 -122
  62. package/dist/hooks.js +10 -13
  63. package/dist/hooks.js.map +1 -1
  64. package/dist/index.d.ts +60 -13
  65. package/dist/index.js +30 -25
  66. package/dist/index.js.map +1 -1
  67. package/dist/providers.d.ts +21 -3
  68. package/dist/providers.js +4 -3
  69. package/dist/rbac/index.d.ts +210 -139
  70. package/dist/rbac/index.js +17 -13
  71. package/dist/styles/index.js +1 -1
  72. package/dist/theming/runtime.d.ts +1 -13
  73. package/dist/theming/runtime.js +2 -2
  74. package/dist/{timezone-_pgH8qrY.d.ts → timezone-CHhWg6b4.d.ts} +3 -10
  75. package/dist/{types-UU913iLA.d.ts → types-BeoeWV5I.d.ts} +8 -0
  76. package/dist/{types-CEpcvwwF.d.ts → types-CkbwOr4Y.d.ts} +6 -0
  77. package/dist/types.d.ts +2 -2
  78. package/dist/types.js +1 -1
  79. package/dist/{usePublicRouteParams-BJAlWfuJ.d.ts → usePublicRouteParams-i3qtoBgg.d.ts} +38 -17
  80. package/dist/utils.d.ts +4 -5
  81. package/dist/utils.js +17 -19
  82. package/dist/utils.js.map +1 -1
  83. package/docs/api/README.md +21 -17
  84. package/docs/api/modules.md +4191 -2967
  85. package/docs/architecture/database-schema-requirements.md +161 -0
  86. package/docs/components/context-selector.md +126 -0
  87. package/docs/core-concepts/rbac-system.md +3 -3
  88. package/docs/documentation-index.md +2 -4
  89. package/docs/getting-started/cursor-rules.md +2 -1
  90. package/docs/migration/DOCUMENTATION_STRUCTURE.md +441 -0
  91. package/docs/migration/MIGRATION_GUIDE.md +2 -24
  92. package/docs/migration/RBAC_SCOPE_MIGRATION.md +385 -0
  93. package/docs/migration/README.md +52 -6
  94. package/docs/migration/V0.5.190_TO_V0.6.1_MIGRATION.md +1153 -0
  95. package/docs/migration/database-changes-december-2025.md +3 -3
  96. package/docs/pace-mint-fix-auto-selection.md +218 -0
  97. package/docs/pace-mint-rbac-setup.md +391 -0
  98. package/docs/rbac/event-based-apps.md +1 -1
  99. package/docs/rbac/getting-started.md +1 -1
  100. package/docs/rbac/quick-start.md +1 -1
  101. package/docs/rbac/secure-client-protection.md +330 -0
  102. package/docs/standards/README.md +1 -0
  103. package/package.json +4 -3
  104. package/scripts/audit/core/checks/accessibility.cjs +197 -0
  105. package/scripts/audit/core/checks/api-usage.cjs +191 -0
  106. package/scripts/audit/core/checks/bundle.cjs +142 -0
  107. package/scripts/{check-pace-core-compliance.cjs → audit/core/checks/compliance.cjs} +784 -685
  108. package/scripts/audit/core/checks/config.cjs +54 -0
  109. package/scripts/audit/core/checks/coverage.cjs +84 -0
  110. package/scripts/audit/core/checks/dependencies.cjs +985 -0
  111. package/scripts/audit/core/checks/documentation.cjs +268 -0
  112. package/scripts/audit/core/checks/environment.cjs +116 -0
  113. package/scripts/audit/core/checks/error-handling.cjs +340 -0
  114. package/scripts/audit/core/checks/forms.cjs +172 -0
  115. package/scripts/audit/core/checks/heuristics.cjs +68 -0
  116. package/scripts/audit/core/checks/hooks.cjs +334 -0
  117. package/scripts/audit/core/checks/imports.cjs +244 -0
  118. package/scripts/audit/core/checks/performance.cjs +325 -0
  119. package/scripts/audit/core/checks/routes.cjs +117 -0
  120. package/scripts/audit/core/checks/state.cjs +130 -0
  121. package/scripts/audit/core/checks/structure.cjs +65 -0
  122. package/scripts/audit/core/checks/style.cjs +584 -0
  123. package/scripts/audit/core/checks/testing.cjs +122 -0
  124. package/scripts/audit/core/checks/typescript.cjs +61 -0
  125. package/scripts/audit/core/scanner.cjs +199 -0
  126. package/scripts/audit/core/utils.cjs +137 -0
  127. package/scripts/audit/index.cjs +223 -0
  128. package/scripts/audit/reporters/console.cjs +151 -0
  129. package/scripts/audit/reporters/json.cjs +54 -0
  130. package/scripts/audit/reporters/markdown.cjs +124 -0
  131. package/scripts/audit-consuming-app.cjs +61 -936
  132. package/scripts/build-docs/build-decision.js +240 -0
  133. package/scripts/build-docs/cache-utils.js +105 -0
  134. package/scripts/build-docs/content-normalization.js +150 -0
  135. package/scripts/build-docs/file-utils.js +105 -0
  136. package/scripts/build-docs/git-utils.js +86 -0
  137. package/scripts/build-docs/hash-utils.js +116 -0
  138. package/scripts/build-docs/typedoc-runner.js +220 -0
  139. package/scripts/build-docs-incremental.js +77 -913
  140. package/scripts/utils/command-runner.js +16 -11
  141. package/scripts/validate-formats.js +61 -56
  142. package/scripts/validate-master.js +74 -69
  143. package/scripts/validate-pre-publish.js +70 -65
  144. package/src/__tests__/hooks/usePermissions.test.ts +2 -2
  145. package/src/components/Alert/Alert.test.tsx +12 -18
  146. package/src/components/Alert/Alert.tsx +5 -7
  147. package/src/components/Avatar/Avatar.test.tsx +4 -4
  148. package/src/components/Badge/Badge.tsx +14 -0
  149. package/src/components/Button/Button.tsx +22 -0
  150. package/src/components/Calendar/Calendar.tsx +8 -2
  151. package/src/components/Card/Card.tsx +4 -0
  152. package/src/components/Checkbox/Checkbox.test.tsx +12 -12
  153. package/src/components/Checkbox/Checkbox.tsx +2 -2
  154. package/src/components/ContextSelector/ContextSelector.tsx +384 -0
  155. package/src/components/ContextSelector/index.ts +3 -0
  156. package/src/components/DataTable/DataTable.tsx +38 -4
  157. package/src/components/DataTable/__tests__/DataTableCore.test-setup.ts +5 -6
  158. package/src/components/DataTable/__tests__/pagination.modes.test.tsx +18 -4
  159. package/src/components/DataTable/__tests__/test-utils/sharedTestUtils.tsx +2 -3
  160. package/src/components/DataTable/components/AccessDeniedPage.tsx +16 -25
  161. package/src/components/DataTable/components/ActionButtons.tsx +10 -7
  162. package/src/components/DataTable/components/BulkOperationsDropdown.tsx +1 -1
  163. package/src/components/DataTable/components/ColumnFilter.tsx +10 -0
  164. package/src/components/DataTable/components/ColumnVisibilityDropdown.tsx +12 -0
  165. package/src/components/DataTable/components/DataTableBody.tsx +8 -0
  166. package/src/components/DataTable/components/DataTableCore.tsx +196 -554
  167. package/src/components/DataTable/components/DataTableErrorBoundary.tsx +11 -0
  168. package/src/components/DataTable/components/DataTableLayout.tsx +559 -0
  169. package/src/components/DataTable/components/DataTableModals.tsx +8 -0
  170. package/src/components/DataTable/components/DataTableToolbar.tsx +8 -0
  171. package/src/components/DataTable/components/DraggableColumnHeader.tsx +12 -0
  172. package/src/components/DataTable/components/EditFields.tsx +307 -0
  173. package/src/components/DataTable/components/EditableRow.tsx +8 -0
  174. package/src/components/DataTable/components/EmptyState.tsx +10 -0
  175. package/src/components/DataTable/components/FilterRow.tsx +12 -0
  176. package/src/components/DataTable/components/GroupHeader.tsx +12 -0
  177. package/src/components/DataTable/components/GroupingDropdown.tsx +12 -0
  178. package/src/components/DataTable/components/ImportModal.tsx +7 -0
  179. package/src/components/DataTable/components/LoadingState.tsx +6 -0
  180. package/src/components/DataTable/components/PaginationControls.tsx +16 -1
  181. package/src/components/DataTable/components/RowComponent.tsx +391 -0
  182. package/src/components/DataTable/components/UnifiedTableBody.tsx +63 -851
  183. package/src/components/DataTable/components/VirtualizedDataTable.tsx +16 -4
  184. package/src/components/DataTable/components/__tests__/AccessDeniedPage.test.tsx +4 -2
  185. package/src/components/DataTable/components/cellValueUtils.ts +40 -0
  186. package/src/components/DataTable/components/hooks/useImportModalFocus.ts +53 -0
  187. package/src/components/DataTable/components/hooks/usePermissionTracking.ts +126 -0
  188. package/src/components/DataTable/context/DataTableContext.tsx +50 -0
  189. package/src/components/DataTable/core/ColumnFactory.ts +31 -0
  190. package/src/components/DataTable/core/DataTableContext.tsx +32 -1
  191. package/src/components/DataTable/hooks/useColumnOrderPersistence.ts +10 -0
  192. package/src/components/DataTable/hooks/useColumnReordering.ts +12 -0
  193. package/src/components/DataTable/hooks/useColumnVisibilityPersistence.ts +10 -0
  194. package/src/components/DataTable/hooks/useDataTableDataPipeline.ts +16 -0
  195. package/src/components/DataTable/hooks/useDataTablePermissions.ts +127 -33
  196. package/src/components/DataTable/hooks/useDataTableState.ts +35 -1
  197. package/src/components/DataTable/hooks/useEffectiveColumnOrder.ts +12 -0
  198. package/src/components/DataTable/hooks/useServerSideDataEffect.ts +11 -0
  199. package/src/components/DataTable/hooks/useTableColumns.ts +8 -0
  200. package/src/components/DataTable/hooks/useTableHandlers.ts +14 -0
  201. package/src/components/DataTable/styles.ts +6 -6
  202. package/src/components/DataTable/types.ts +6 -10
  203. package/src/components/DataTable/utils/a11yUtils.ts +7 -0
  204. package/src/components/DataTable/utils/debugTools.ts +18 -113
  205. package/src/components/DataTable/utils/errorHandling.ts +12 -0
  206. package/src/components/DataTable/utils/exportUtils.ts +9 -0
  207. package/src/components/DataTable/utils/flexibleImport.ts +12 -48
  208. package/src/components/DataTable/utils/paginationUtils.ts +8 -0
  209. package/src/components/DataTable/utils/performanceUtils.ts +5 -1
  210. package/src/components/Dialog/Dialog.tsx +31 -3
  211. package/src/components/ErrorBoundary/ErrorBoundary.test.tsx +180 -1
  212. package/src/components/ErrorBoundary/ErrorBoundary.tsx +45 -5
  213. package/src/components/ErrorBoundary/ErrorBoundaryContext.tsx +129 -0
  214. package/src/components/ErrorBoundary/index.ts +27 -2
  215. package/src/components/FileDisplay/FileDisplay.tsx +74 -28
  216. package/src/components/FileUpload/FileUpload.tsx +22 -2
  217. package/src/components/Footer/Footer.test.tsx +16 -16
  218. package/src/components/Footer/Footer.tsx +14 -11
  219. package/src/components/Form/Form.tsx +1 -0
  220. package/src/components/Header/Header.test.tsx +43 -73
  221. package/src/components/Header/Header.tsx +59 -49
  222. package/src/components/Input/Input.test.tsx +2 -2
  223. package/src/components/Input/Input.tsx +8 -4
  224. package/src/components/LoadingSpinner/LoadingSpinner.test.tsx +4 -4
  225. package/src/components/LoginForm/LoginForm.tsx +4 -0
  226. package/src/components/NavigationMenu/NavigationMenu.tsx +14 -513
  227. package/src/components/NavigationMenu/types.ts +56 -0
  228. package/src/components/NavigationMenu/useNavigationFiltering.ts +390 -0
  229. package/src/components/PaceAppLayout/PaceAppLayout.integration.test.tsx +10 -19
  230. package/src/components/PaceAppLayout/PaceAppLayout.performance.test.tsx +2 -2
  231. package/src/components/PaceAppLayout/PaceAppLayout.security.test.tsx +5 -5
  232. package/src/components/PaceAppLayout/PaceAppLayout.test.tsx +13 -11
  233. package/src/components/PaceAppLayout/PaceAppLayout.tsx +167 -44
  234. package/src/components/PaceAppLayout/README.md +14 -17
  235. package/src/components/PaceAppLayout/test-setup.tsx +3 -4
  236. package/src/components/PaceLoginPage/PaceLoginPage.tsx +3 -0
  237. package/src/components/PasswordChange/PasswordChangeForm.tsx +9 -0
  238. package/src/components/ProtectedRoute/ProtectedRoute.tsx +3 -9
  239. package/src/components/PublicLayout/PublicPageLayout.tsx +2 -5
  240. package/src/components/PublicLayout/PublicPageProvider.tsx +4 -0
  241. package/src/components/Select/Select.tsx +80 -434
  242. package/src/components/Select/context.ts +23 -0
  243. package/src/components/Select/hooks/useSelectEvents.ts +87 -0
  244. package/src/components/Select/hooks/useSelectSearch.ts +91 -0
  245. package/src/components/Select/hooks/useSelectState.ts +104 -0
  246. package/src/components/Select/index.ts +9 -1
  247. package/src/components/Select/types.ts +123 -0
  248. package/src/components/Select/utils/text.ts +26 -0
  249. package/src/components/SessionRestorationLoader/SessionRestorationLoader.tsx +4 -5
  250. package/src/components/Switch/Switch.tsx +4 -4
  251. package/src/components/Tabs/Tabs.tsx +1 -1
  252. package/src/components/Toast/Toast.tsx +4 -0
  253. package/src/components/Tooltip/Tooltip.tsx +2 -2
  254. package/src/components/UserMenu/UserMenu.test.tsx +24 -11
  255. package/src/components/UserMenu/UserMenu.tsx +21 -18
  256. package/src/components/index.ts +7 -7
  257. package/src/eslint-rules/pace-core-compliance.cjs +106 -0
  258. package/src/hooks/__tests__/index.unit.test.ts +2 -5
  259. package/src/hooks/__tests__/useAppConfig.unit.test.ts +4 -98
  260. package/src/hooks/index.ts +1 -2
  261. package/src/hooks/public/usePublicEvent.ts +4 -0
  262. package/src/hooks/public/usePublicEventLogo.ts +4 -0
  263. package/src/hooks/public/usePublicFileDisplay.ts +4 -0
  264. package/src/hooks/public/usePublicRouteParams.ts +4 -0
  265. package/src/hooks/services/useAuth.ts +32 -0
  266. package/src/hooks/services/useCurrentEvent.ts +6 -0
  267. package/src/hooks/services/useCurrentOrganisation.ts +6 -0
  268. package/src/hooks/useAppConfig.ts +15 -30
  269. package/src/hooks/useDebounce.ts +9 -0
  270. package/src/hooks/useEventTheme.ts +6 -0
  271. package/src/hooks/useFileDisplay.ts +81 -50
  272. package/src/hooks/useFileReference.ts +25 -7
  273. package/src/hooks/useFileUrl.ts +11 -1
  274. package/src/hooks/useFocusManagement.ts +14 -0
  275. package/src/hooks/useFocusTrap.ts +3 -0
  276. package/src/hooks/useInactivityTracker.ts +3 -0
  277. package/src/hooks/useKeyboardShortcuts.ts +4 -0
  278. package/src/hooks/useOrganisationPermissions.ts +4 -0
  279. package/src/hooks/useOrganisationSecurity.ts +4 -0
  280. package/src/hooks/usePerformanceMonitor.ts +4 -0
  281. package/src/hooks/usePermissionCache.ts +7 -0
  282. package/src/hooks/useQueryCache.ts +12 -1
  283. package/src/hooks/useSessionRestoration.ts +4 -0
  284. package/src/hooks/useStorage.ts +4 -0
  285. package/src/hooks/useToast.ts +1 -1
  286. package/src/index.ts +6 -6
  287. package/src/providers/__tests__/OrganisationProvider.test.tsx +92 -70
  288. package/src/providers/services/AuthServiceProvider.tsx +35 -7
  289. package/src/providers/services/EventServiceProvider.tsx +51 -5
  290. package/src/providers/services/InactivityServiceProvider.tsx +18 -0
  291. package/src/providers/services/OrganisationServiceProvider.tsx +18 -0
  292. package/src/providers/services/UnifiedAuthProvider.tsx +126 -134
  293. package/src/providers/services/__tests__/AuthServiceProvider.integration.test.tsx +29 -13
  294. package/src/rbac/README.md +1 -1
  295. package/src/rbac/__tests__/adapters.comprehensive.test.tsx +1 -1
  296. package/src/rbac/__tests__/scenarios.user-role.test.tsx +4 -5
  297. package/src/rbac/adapters.tsx +12 -3
  298. package/src/rbac/api.test.ts +59 -51
  299. package/src/rbac/api.ts +246 -167
  300. package/src/rbac/components/NavigationProvider.tsx +4 -1
  301. package/src/rbac/components/PagePermissionGuard.tsx +185 -17
  302. package/src/rbac/components/RoleBasedRouter.tsx +5 -1
  303. package/src/rbac/components/SecureDataProvider.test.tsx +84 -49
  304. package/src/rbac/components/SecureDataProvider.tsx +20 -5
  305. package/src/rbac/components/__tests__/PagePermissionGuard.race-condition.test.tsx +24 -14
  306. package/src/rbac/components/__tests__/PagePermissionGuard.test.tsx +7 -0
  307. package/src/rbac/components/__tests__/PagePermissionGuard.verification.test.tsx +14 -6
  308. package/src/rbac/components/__tests__/RoleBasedRouter.test.tsx +15 -4
  309. package/src/rbac/components/__tests__/SecureDataProvider.fixed.test.tsx +148 -24
  310. package/src/rbac/components/__tests__/SecureDataProvider.test.tsx +81 -15
  311. package/src/rbac/engine.ts +38 -14
  312. package/src/rbac/hooks/__tests__/useSecureSupabase.test.ts +32 -21
  313. package/src/rbac/hooks/permissions/index.ts +7 -0
  314. package/src/rbac/hooks/permissions/useAccessLevel.ts +105 -0
  315. package/src/rbac/hooks/permissions/useCachedPermissions.ts +79 -0
  316. package/src/rbac/hooks/permissions/useCan.ts +377 -0
  317. package/src/rbac/hooks/permissions/useHasAllPermissions.ts +90 -0
  318. package/src/rbac/hooks/permissions/useHasAnyPermission.ts +90 -0
  319. package/src/rbac/hooks/permissions/useMultiplePermissions.ts +93 -0
  320. package/src/rbac/hooks/permissions/usePermissions.ts +253 -0
  321. package/src/rbac/hooks/useCan.test.ts +64 -66
  322. package/src/rbac/hooks/usePermissions.ts +14 -995
  323. package/src/rbac/hooks/useRBAC.test.ts +1 -5
  324. package/src/rbac/hooks/useRBAC.ts +36 -37
  325. package/src/rbac/hooks/useResolvedScope.test.ts +120 -35
  326. package/src/rbac/hooks/useResolvedScope.ts +35 -40
  327. package/src/rbac/hooks/useResourcePermissions.test.ts +54 -18
  328. package/src/rbac/hooks/useResourcePermissions.ts +14 -4
  329. package/src/rbac/hooks/useSecureSupabase.ts +27 -7
  330. package/src/rbac/index.ts +7 -0
  331. package/src/rbac/permissions.ts +0 -30
  332. package/src/rbac/secureClient.test.ts +22 -18
  333. package/src/rbac/secureClient.ts +294 -68
  334. package/src/rbac/security.ts +0 -17
  335. package/src/rbac/types.ts +9 -0
  336. package/src/rbac/utils/__tests__/contextValidator.test.ts +64 -86
  337. package/src/rbac/utils/clientSecurity.ts +93 -0
  338. package/src/rbac/utils/contextValidator.ts +77 -168
  339. package/src/services/AuthService.ts +39 -7
  340. package/src/services/EventService.ts +186 -54
  341. package/src/services/OrganisationService.ts +81 -14
  342. package/src/services/__tests__/EventService.test.ts +1 -2
  343. package/src/services/base/BaseService.ts +3 -0
  344. package/src/theming/__tests__/parseEventColours.test.ts +6 -9
  345. package/src/theming/parseEventColours.ts +5 -19
  346. package/src/types/vitest-globals.d.ts +51 -26
  347. package/src/utils/__mocks__/supabaseMock.ts +1 -3
  348. package/src/utils/__tests__/formatting.unit.test.ts +4 -4
  349. package/src/utils/__tests__/index.unit.test.ts +2 -2
  350. package/src/utils/audit/audit.ts +0 -3
  351. package/src/utils/core/cn.ts +1 -1
  352. package/src/utils/dynamic/dynamicUtils.ts +7 -4
  353. package/src/utils/file-reference/index.ts +53 -1
  354. package/src/utils/formatting/formatting.ts +8 -18
  355. package/src/utils/index.ts +0 -1
  356. package/dist/chunk-3QRJFVBR.js.map +0 -1
  357. package/dist/chunk-3XTALGJF.js.map +0 -1
  358. package/dist/chunk-4N5C5XZU.js.map +0 -1
  359. package/dist/chunk-4ZC4GX36.js.map +0 -1
  360. package/dist/chunk-7D4SUZUM.js +0 -38
  361. package/dist/chunk-BYFSK72L.js.map +0 -1
  362. package/dist/chunk-EXUD6RNJ.js +0 -451
  363. package/dist/chunk-EXUD6RNJ.js.map +0 -1
  364. package/dist/chunk-GLK6VM3F.js.map +0 -1
  365. package/dist/chunk-I7PSE6JW.js.map +0 -1
  366. package/dist/chunk-JBKQ3SAO.js.map +0 -1
  367. package/dist/chunk-KNC55RTG.js.map +0 -1
  368. package/dist/chunk-LXQLPRQ2.js.map +0 -1
  369. package/dist/chunk-R77UEZ4E.js.map +0 -1
  370. package/dist/chunk-SQGMNID3.js.map +0 -1
  371. package/dist/chunk-T33XF5ZC.js +0 -12922
  372. package/dist/chunk-T33XF5ZC.js.map +0 -1
  373. package/dist/chunk-XM25TVIE.js.map +0 -1
  374. package/docs/api/classes/ColumnFactory.md +0 -243
  375. package/docs/api/classes/ErrorBoundary.md +0 -144
  376. package/docs/api/classes/InvalidScopeError.md +0 -73
  377. package/docs/api/classes/Logger.md +0 -178
  378. package/docs/api/classes/MissingUserContextError.md +0 -66
  379. package/docs/api/classes/OrganisationContextRequiredError.md +0 -66
  380. package/docs/api/classes/PermissionDeniedError.md +0 -73
  381. package/docs/api/classes/RBACAuditManager.md +0 -297
  382. package/docs/api/classes/RBACCache.md +0 -322
  383. package/docs/api/classes/RBACEngine.md +0 -171
  384. package/docs/api/classes/RBACError.md +0 -76
  385. package/docs/api/classes/RBACNotInitializedError.md +0 -66
  386. package/docs/api/classes/SecureSupabaseClient.md +0 -160
  387. package/docs/api/classes/StorageUtils.md +0 -328
  388. package/docs/api/enums/FileCategory.md +0 -184
  389. package/docs/api/enums/LogLevel.md +0 -54
  390. package/docs/api/enums/RBACErrorCode.md +0 -228
  391. package/docs/api/enums/RPCFunction.md +0 -118
  392. package/docs/api/interfaces/AddressFieldProps.md +0 -241
  393. package/docs/api/interfaces/AddressFieldRef.md +0 -94
  394. package/docs/api/interfaces/AggregateConfig.md +0 -43
  395. package/docs/api/interfaces/AutocompleteOptions.md +0 -75
  396. package/docs/api/interfaces/AvatarProps.md +0 -128
  397. package/docs/api/interfaces/BadgeProps.md +0 -27
  398. package/docs/api/interfaces/ButtonProps.md +0 -53
  399. package/docs/api/interfaces/CalendarProps.md +0 -70
  400. package/docs/api/interfaces/CardProps.md +0 -66
  401. package/docs/api/interfaces/ColorPalette.md +0 -7
  402. package/docs/api/interfaces/ColorShade.md +0 -66
  403. package/docs/api/interfaces/ComplianceResult.md +0 -30
  404. package/docs/api/interfaces/DataAccessRecord.md +0 -96
  405. package/docs/api/interfaces/DataRecord.md +0 -11
  406. package/docs/api/interfaces/DataTableAction.md +0 -249
  407. package/docs/api/interfaces/DataTableColumn.md +0 -504
  408. package/docs/api/interfaces/DataTableProps.md +0 -625
  409. package/docs/api/interfaces/DataTableToolbarButton.md +0 -96
  410. package/docs/api/interfaces/DatabaseComplianceResult.md +0 -85
  411. package/docs/api/interfaces/DatabaseIssue.md +0 -41
  412. package/docs/api/interfaces/EmptyStateConfig.md +0 -61
  413. package/docs/api/interfaces/EnhancedNavigationMenuProps.md +0 -235
  414. package/docs/api/interfaces/EventAppRoleData.md +0 -71
  415. package/docs/api/interfaces/ExportColumn.md +0 -90
  416. package/docs/api/interfaces/ExportOptions.md +0 -126
  417. package/docs/api/interfaces/FileDisplayProps.md +0 -249
  418. package/docs/api/interfaces/FileMetadata.md +0 -129
  419. package/docs/api/interfaces/FileReference.md +0 -118
  420. package/docs/api/interfaces/FileSizeLimits.md +0 -7
  421. package/docs/api/interfaces/FileUploadOptions.md +0 -139
  422. package/docs/api/interfaces/FileUploadProps.md +0 -293
  423. package/docs/api/interfaces/FooterProps.md +0 -105
  424. package/docs/api/interfaces/FormFieldProps.md +0 -166
  425. package/docs/api/interfaces/FormProps.md +0 -113
  426. package/docs/api/interfaces/GrantEventAppRoleParams.md +0 -122
  427. package/docs/api/interfaces/InactivityWarningModalProps.md +0 -115
  428. package/docs/api/interfaces/InputProps.md +0 -53
  429. package/docs/api/interfaces/LabelProps.md +0 -107
  430. package/docs/api/interfaces/LoggerConfig.md +0 -62
  431. package/docs/api/interfaces/LoginFormProps.md +0 -184
  432. package/docs/api/interfaces/NavigationAccessRecord.md +0 -107
  433. package/docs/api/interfaces/NavigationContextType.md +0 -164
  434. package/docs/api/interfaces/NavigationGuardProps.md +0 -139
  435. package/docs/api/interfaces/NavigationItem.md +0 -120
  436. package/docs/api/interfaces/NavigationMenuProps.md +0 -221
  437. package/docs/api/interfaces/NavigationProviderProps.md +0 -117
  438. package/docs/api/interfaces/Organisation.md +0 -140
  439. package/docs/api/interfaces/OrganisationContextType.md +0 -388
  440. package/docs/api/interfaces/OrganisationMembership.md +0 -140
  441. package/docs/api/interfaces/OrganisationProviderProps.md +0 -76
  442. package/docs/api/interfaces/OrganisationSecurityError.md +0 -62
  443. package/docs/api/interfaces/PaceAppLayoutProps.md +0 -406
  444. package/docs/api/interfaces/PaceLoginPageProps.md +0 -47
  445. package/docs/api/interfaces/PageAccessRecord.md +0 -85
  446. package/docs/api/interfaces/PagePermissionContextType.md +0 -140
  447. package/docs/api/interfaces/PagePermissionGuardProps.md +0 -153
  448. package/docs/api/interfaces/PagePermissionProviderProps.md +0 -119
  449. package/docs/api/interfaces/PaletteData.md +0 -41
  450. package/docs/api/interfaces/ParsedAddress.md +0 -120
  451. package/docs/api/interfaces/PermissionEnforcerProps.md +0 -153
  452. package/docs/api/interfaces/ProgressProps.md +0 -42
  453. package/docs/api/interfaces/ProtectedRouteProps.md +0 -97
  454. package/docs/api/interfaces/PublicPageFooterProps.md +0 -112
  455. package/docs/api/interfaces/PublicPageHeaderProps.md +0 -125
  456. package/docs/api/interfaces/PublicPageLayoutProps.md +0 -198
  457. package/docs/api/interfaces/QuickFix.md +0 -52
  458. package/docs/api/interfaces/RBACAccessValidateParams.md +0 -52
  459. package/docs/api/interfaces/RBACAccessValidateResult.md +0 -41
  460. package/docs/api/interfaces/RBACAuditLogParams.md +0 -85
  461. package/docs/api/interfaces/RBACAuditLogResult.md +0 -52
  462. package/docs/api/interfaces/RBACConfig.md +0 -133
  463. package/docs/api/interfaces/RBACContext.md +0 -52
  464. package/docs/api/interfaces/RBACLogger.md +0 -112
  465. package/docs/api/interfaces/RBACPageAccessCheckParams.md +0 -74
  466. package/docs/api/interfaces/RBACPerformanceMetrics.md +0 -138
  467. package/docs/api/interfaces/RBACPermissionCheckParams.md +0 -74
  468. package/docs/api/interfaces/RBACPermissionCheckResult.md +0 -52
  469. package/docs/api/interfaces/RBACPermissionsGetParams.md +0 -63
  470. package/docs/api/interfaces/RBACPermissionsGetResult.md +0 -63
  471. package/docs/api/interfaces/RBACResult.md +0 -58
  472. package/docs/api/interfaces/RBACRoleGrantParams.md +0 -63
  473. package/docs/api/interfaces/RBACRoleGrantResult.md +0 -52
  474. package/docs/api/interfaces/RBACRoleRevokeParams.md +0 -63
  475. package/docs/api/interfaces/RBACRoleRevokeResult.md +0 -52
  476. package/docs/api/interfaces/RBACRoleValidateParams.md +0 -52
  477. package/docs/api/interfaces/RBACRoleValidateResult.md +0 -63
  478. package/docs/api/interfaces/RBACRolesListParams.md +0 -52
  479. package/docs/api/interfaces/RBACRolesListResult.md +0 -74
  480. package/docs/api/interfaces/RBACSessionTrackParams.md +0 -74
  481. package/docs/api/interfaces/RBACSessionTrackResult.md +0 -52
  482. package/docs/api/interfaces/ResourcePermissions.md +0 -155
  483. package/docs/api/interfaces/RevokeEventAppRoleParams.md +0 -100
  484. package/docs/api/interfaces/RoleBasedRouterContextType.md +0 -151
  485. package/docs/api/interfaces/RoleBasedRouterProps.md +0 -156
  486. package/docs/api/interfaces/RoleManagementResult.md +0 -52
  487. package/docs/api/interfaces/RouteAccessRecord.md +0 -107
  488. package/docs/api/interfaces/RouteConfig.md +0 -134
  489. package/docs/api/interfaces/RuntimeComplianceResult.md +0 -55
  490. package/docs/api/interfaces/SecureDataContextType.md +0 -168
  491. package/docs/api/interfaces/SecureDataProviderProps.md +0 -132
  492. package/docs/api/interfaces/SessionRestorationLoaderProps.md +0 -34
  493. package/docs/api/interfaces/SetupIssue.md +0 -41
  494. package/docs/api/interfaces/StorageConfig.md +0 -41
  495. package/docs/api/interfaces/StorageFileInfo.md +0 -74
  496. package/docs/api/interfaces/StorageFileMetadata.md +0 -151
  497. package/docs/api/interfaces/StorageListOptions.md +0 -99
  498. package/docs/api/interfaces/StorageListResult.md +0 -41
  499. package/docs/api/interfaces/StorageUploadOptions.md +0 -101
  500. package/docs/api/interfaces/StorageUploadResult.md +0 -63
  501. package/docs/api/interfaces/StorageUrlOptions.md +0 -60
  502. package/docs/api/interfaces/StyleImport.md +0 -19
  503. package/docs/api/interfaces/SwitchProps.md +0 -34
  504. package/docs/api/interfaces/TabsContentProps.md +0 -9
  505. package/docs/api/interfaces/TabsListProps.md +0 -9
  506. package/docs/api/interfaces/TabsProps.md +0 -9
  507. package/docs/api/interfaces/TabsTriggerProps.md +0 -50
  508. package/docs/api/interfaces/TextareaProps.md +0 -53
  509. package/docs/api/interfaces/ToastActionElement.md +0 -9
  510. package/docs/api/interfaces/ToastProps.md +0 -9
  511. package/docs/api/interfaces/UnifiedAuthContextType.md +0 -820
  512. package/docs/api/interfaces/UnifiedAuthProviderProps.md +0 -171
  513. package/docs/api/interfaces/UseFormDialogOptions.md +0 -62
  514. package/docs/api/interfaces/UseFormDialogReturn.md +0 -117
  515. package/docs/api/interfaces/UseInactivityTrackerOptions.md +0 -136
  516. package/docs/api/interfaces/UseInactivityTrackerReturn.md +0 -123
  517. package/docs/api/interfaces/UsePublicEventLogoOptions.md +0 -87
  518. package/docs/api/interfaces/UsePublicEventLogoReturn.md +0 -81
  519. package/docs/api/interfaces/UsePublicEventOptions.md +0 -34
  520. package/docs/api/interfaces/UsePublicEventReturn.md +0 -68
  521. package/docs/api/interfaces/UsePublicFileDisplayOptions.md +0 -47
  522. package/docs/api/interfaces/UsePublicFileDisplayReturn.md +0 -120
  523. package/docs/api/interfaces/UsePublicRouteParamsReturn.md +0 -94
  524. package/docs/api/interfaces/UseResolvedScopeOptions.md +0 -47
  525. package/docs/api/interfaces/UseResolvedScopeReturn.md +0 -47
  526. package/docs/api/interfaces/UseResourcePermissionsOptions.md +0 -34
  527. package/docs/api/interfaces/UserEventAccess.md +0 -118
  528. package/docs/api/interfaces/UserMenuProps.md +0 -86
  529. package/docs/api/interfaces/UserProfile.md +0 -63
  530. package/docs/migration/quick-migration-guide.md +0 -356
  531. package/docs/migration/service-architecture.md +0 -281
  532. package/src/components/EventSelector/EventSelector.test.tsx +0 -720
  533. package/src/components/EventSelector/EventSelector.tsx +0 -420
  534. package/src/components/EventSelector/index.ts +0 -3
  535. package/src/components/OrganisationSelector/OrganisationSelector.test.tsx +0 -784
  536. package/src/components/OrganisationSelector/OrganisationSelector.tsx +0 -324
  537. package/src/components/OrganisationSelector/index.ts +0 -9
  538. package/src/hooks/__tests__/useSecureDataAccess.unit.test.tsx +0 -680
  539. package/src/hooks/useSecureDataAccess.test.ts +0 -559
  540. package/src/hooks/useSecureDataAccess.ts +0 -681
  541. /package/dist/{DataTable-DQ7RSOHE.js.map → DataTable-THFPBKTP.js.map} +0 -0
  542. /package/dist/{UnifiedAuthProvider-ATAP5UTR.js.map → UnifiedAuthProvider-KAGUYQ4J.js.map} +0 -0
  543. /package/dist/{api-N774RPUA.js.map → api-IAGWF3ZG.js.map} +0 -0
  544. /package/dist/{audit-B5P6FFIR.js.map → audit-V53FV5AG.js.map} +0 -0
  545. /package/dist/{chunk-7D4SUZUM.js.map → chunk-DGUM43GV.js.map} +0 -0
  546. /package/docs/migration/{organisation-context-timing-fix.md → V0.3.44_organisation-context-timing-fix.md} +0 -0
  547. /package/docs/migration/{rbac-migration.md → V0.4.0_rbac-migration.md} +0 -0
  548. /package/docs/migration/{person-scoped-profiles-migration-guide.md → V0.5.190_person-scoped-profiles-migration-guide.md} +0 -0
  549. /package/docs/migration/{REACT_19_MIGRATION.md → V0.6.0_REACT_19_MIGRATION.md} +0 -0
@@ -0,0 +1,334 @@
1
+ #!/usr/bin/env node
2
+
3
+ /**
4
+ * React Hooks Compliance Check Module
5
+ * @package @jmruthers/pace-core
6
+ * @module Audit/Checks/Hooks
7
+ *
8
+ * Checks for:
9
+ * - Hooks called conditionally or after early returns
10
+ * - Hooks called in loops
11
+ * - Missing dependencies in useEffect/useMemo/useCallback
12
+ * - Hooks called in wrong order
13
+ */
14
+
15
+ const fs = require('fs');
16
+ const { getRelativePath, getLineNumber } = require('../utils.cjs');
17
+
18
+ const hooksCheck = {
19
+ name: 'hooks',
20
+ description: 'React hooks compliance (conditional calls, missing dependencies, etc.)',
21
+ severity: 'error',
22
+
23
+ async run(context) {
24
+ const { projectRoot, files } = context;
25
+ const issues = [];
26
+ const warnings = [];
27
+ const suggestions = [];
28
+
29
+ if (!files || files.length === 0) {
30
+ return { issues, warnings, suggestions };
31
+ }
32
+
33
+ // React hooks that must follow rules
34
+ // Separate actual React hooks from custom hooks
35
+ const actualReactHooks = [
36
+ 'useState', 'useEffect', 'useContext', 'useReducer', 'useCallback',
37
+ 'useMemo', 'useRef', 'useImperativeHandle', 'useLayoutEffect',
38
+ 'useDebugValue'
39
+ ];
40
+
41
+ const customHooks = [
42
+ 'useUnifiedAuth', 'useOrganisations', 'useEvents',
43
+ 'usePermissions', 'useCan', 'useSecureSupabase', 'useToast',
44
+ 'useDebounce', 'useZodForm', 'useFileReference', 'useRBAC'
45
+ ];
46
+
47
+ // Combined list for pattern matching
48
+ const hookNames = [...actualReactHooks, ...customHooks];
49
+
50
+ const hookPattern = new RegExp(`\\b(${hookNames.join('|')})\\s*\\(`, 'g');
51
+
52
+ for (const filePath of files) {
53
+ try {
54
+ // Only check React component files
55
+ if (!filePath.match(/\.(tsx|jsx)$/)) {
56
+ continue;
57
+ }
58
+
59
+ const content = fs.readFileSync(filePath, 'utf8');
60
+ const relativePath = getRelativePath(filePath, projectRoot);
61
+ const normalizedPath = relativePath.replace(/\\/g, '/');
62
+
63
+ // Skip root-level src directory - in pace-core repository, this is a demo/showcase app
64
+ const isRootSrc = normalizedPath.startsWith('src/') && !normalizedPath.includes('packages/');
65
+ if (isRootSrc) {
66
+ continue; // Skip demo app files
67
+ }
68
+
69
+ // Skip scripts directory - utility scripts don't need hooks validation
70
+ const isScript = normalizedPath.startsWith('scripts/') || normalizedPath.includes('/scripts/');
71
+ if (isScript) {
72
+ continue; // Skip script files
73
+ }
74
+
75
+ // Check if this is a pace-core package file
76
+ // Skip "hook-after-return" check for pace-core files - these are false positives.
77
+ // The detection logic has issues with complex nested structures in library code.
78
+ // Other hook checks (conditional calls, missing dependencies) still apply to pace-core.
79
+ const isPaceCorePackage = normalizedPath.includes('packages/core/') || normalizedPath.startsWith('packages/core/');
80
+
81
+ // Check for hooks
82
+ if (!hookPattern.test(content)) {
83
+ continue; // No hooks in this file
84
+ }
85
+
86
+ // Find all hook calls
87
+ const hookCalls = [];
88
+ let match;
89
+ const regex = new RegExp(`\\b(${hookNames.join('|')})\\s*\\(`, 'g');
90
+ while ((match = regex.exec(content)) !== null) {
91
+ hookCalls.push({
92
+ name: match[1],
93
+ index: match.index,
94
+ line: getLineNumber(content, match.index)
95
+ });
96
+ }
97
+
98
+ if (hookCalls.length === 0) {
99
+ continue;
100
+ }
101
+
102
+ // Check for hooks called conditionally
103
+ for (const hookCall of hookCalls) {
104
+ const beforeHook = content.substring(0, hookCall.index);
105
+ const linesBefore = beforeHook.split('\n');
106
+ const currentLine = linesBefore[linesBefore.length - 1];
107
+
108
+ // Check if hook is in a conditional
109
+ const isInConditional = /if\s*\(|else\s*\{|switch\s*\(|case\s+.*:|for\s*\(|while\s*\(|\.map\s*\(/.test(currentLine);
110
+
111
+ if (isInConditional) {
112
+ issues.push({
113
+ type: 'hook-in-conditional',
114
+ file: relativePath,
115
+ line: hookCall.line,
116
+ message: `Hook '${hookCall.name}' is called conditionally or in a loop`,
117
+ recommendation: 'Hooks must be called at the top level of the component, not conditionally or in loops'
118
+ });
119
+ }
120
+
121
+ // Check if there's an early return before this hook at the component level
122
+ // Only flag returns at the component function level, not inside nested functions
123
+ const functionStart = beforeHook.lastIndexOf('function') > beforeHook.lastIndexOf('=>') ?
124
+ beforeHook.lastIndexOf('function') : beforeHook.lastIndexOf('=>');
125
+
126
+ if (functionStart !== -1) {
127
+ const functionBody = content.substring(functionStart, hookCall.index);
128
+
129
+ // Find all return statements and check if they're at component level (not in nested functions)
130
+ // Only flag CONDITIONAL early returns (guard clauses), not the final return statement
131
+ const returnPattern = /\breturn\s+[^;]+;|\breturn\s*;|\breturn\s+\(/g;
132
+ let returnMatch;
133
+ let hasComponentLevelReturn = false;
134
+
135
+ while ((returnMatch = returnPattern.exec(functionBody)) !== null) {
136
+ const returnIndex = returnMatch.index;
137
+ const beforeReturn = functionBody.substring(Math.max(0, returnIndex - 200), returnIndex);
138
+ const afterReturn = functionBody.substring(returnIndex, Math.min(functionBody.length, returnIndex + 100));
139
+
140
+ // Check if this is a CONDITIONAL early return (guard clause)
141
+ // Pattern: if (...) return ...; or if (...) { return ...; }
142
+ // NOT the final return statement of the component
143
+ const isConditionalReturn = /\bif\s*\([^)]+\)\s*(return|{[\s\S]*?return)/.test(beforeReturn) ||
144
+ /\belse\s+if\s*\([^)]+\)\s*(return|{[\s\S]*?return)/.test(beforeReturn) ||
145
+ /\belse\s*{\s*return/.test(beforeReturn) ||
146
+ /\?\s*\([^)]*\)\s*=>\s*{?\s*return/.test(beforeReturn); // Ternary operator
147
+
148
+ // If it's not a conditional return, it's likely the final return - skip it
149
+ if (!isConditionalReturn) {
150
+ continue;
151
+ }
152
+
153
+ // Check if return is inside a nested function by looking for patterns
154
+ const isInHookInitializer = /useState\s*\(\s*\([^)]*\)\s*=>/.test(beforeReturn);
155
+ const isInEffectCallback = /useEffect\s*\(\s*\([^)]*\)\s*=>/.test(beforeReturn);
156
+ const isInQueryFn = /queryFn\s*:\s*\([^)]*\)\s*=>|useQuery\s*\(\s*\{[^}]*queryFn/.test(beforeReturn);
157
+ const isInArrayMethod = /\.(map|filter|find|reduce|forEach|some|every)\s*\(\s*\([^)]*\)\s*=>/.test(beforeReturn);
158
+ const isInIIFE = /\(\s*\([^)]*\)\s*=>/.test(beforeReturn);
159
+ const isInFunctionExpr = /\bfunction\s*\([^)]*\)\s*\{/.test(beforeReturn);
160
+ const isInUseMemo = /useMemo\s*\(\s*\([^)]*\)\s*=>/.test(beforeReturn);
161
+ const isInUseCallback = /useCallback\s*\(\s*\([^)]*\)\s*=>/.test(beforeReturn);
162
+
163
+ // Check if return is inside a switch case (also nested)
164
+ const isInSwitchCase = /\bcase\s+[^:]+:\s*[\s\S]*?return/.test(beforeReturn);
165
+
166
+ // If return is NOT in any nested function pattern, it's a component-level conditional return
167
+ if (!isInHookInitializer && !isInEffectCallback && !isInQueryFn &&
168
+ !isInArrayMethod && !isInIIFE && !isInFunctionExpr &&
169
+ !isInUseMemo && !isInUseCallback && !isInSwitchCase) {
170
+ hasComponentLevelReturn = true;
171
+ break;
172
+ }
173
+ }
174
+
175
+ if (hasComponentLevelReturn && !isPaceCorePackage) {
176
+ // Skip this check for pace-core files - false positives due to complex nested structures
177
+ issues.push({
178
+ type: 'hook-after-return',
179
+ file: relativePath,
180
+ line: hookCall.line,
181
+ message: `Hook '${hookCall.name}' is called after an early return`,
182
+ recommendation: 'All hooks must be called before any conditional returns at the component level. Move hooks to the top of the component, before any component-level conditional returns. Returns inside nested functions (useState initializers, useEffect callbacks, etc.) are fine. If you have guard clauses (if (loading) return <Loading />), move all hooks above these guard clauses.'
183
+ });
184
+ }
185
+ }
186
+ }
187
+
188
+ // Check for missing dependencies in useEffect, useMemo, useCallback
189
+ const dependencyHooks = ['useEffect', 'useMemo', 'useCallback', 'useLayoutEffect'];
190
+ dependencyHooks.forEach(hookName => {
191
+ // Match hook with dependency array - handle multiline and various formats
192
+ const hookRegex = new RegExp(`${hookName}\\s*\\(([^)]*)\\)\\s*,\\s*\\[([^\\]]*)\\]`, 'gs');
193
+ let depMatch;
194
+ while ((depMatch = hookRegex.exec(content)) !== null) {
195
+ const hookBody = depMatch[1]; // The function body or callback
196
+ const deps = depMatch[2].trim();
197
+
198
+ // If dependency array is empty, check if hook only uses stable globals
199
+ if (deps === '') {
200
+ // Extract the actual callback body (handle arrow functions and regular functions)
201
+ let callbackBody = hookBody;
202
+
203
+ // Check if it's an arrow function: () => { ... } or () => ...
204
+ if (hookBody.includes('=>')) {
205
+ const arrowMatch = hookBody.match(/=>\s*\{?([^}]*)\}?$/);
206
+ if (arrowMatch) {
207
+ callbackBody = arrowMatch[1];
208
+ }
209
+ }
210
+
211
+ // Check if callback only uses stable global APIs or imported module-level constants
212
+ // Stable globals that don't need dependencies:
213
+ const stableGlobals = [
214
+ /window\.(location|document|localStorage|sessionStorage|navigator)/,
215
+ /document\.(documentElement|body|getElementById|querySelector)/,
216
+ /localStorage\.(getItem|setItem|removeItem|clear)/,
217
+ /sessionStorage\.(getItem|setItem|removeItem|clear)/,
218
+ /console\.(log|error|warn|info|debug)/,
219
+ /Math\./,
220
+ /Date\./,
221
+ /JSON\./,
222
+ /Object\./,
223
+ /Array\./,
224
+ /String\./,
225
+ /Number\./,
226
+ /Boolean\./,
227
+ /RegExp\./,
228
+ /Error\./,
229
+ /Promise\./,
230
+ /Symbol\./,
231
+ /Reflect\./,
232
+ /Proxy\./
233
+ ];
234
+
235
+ // Check for imported module-level constants (classes, functions, constants)
236
+ // Pattern: ImportedClass.method() or importedFunction() or importedConstant
237
+ const importedModulePattern = /[A-Z]\w+\.\w+\(|^[A-Z]\w+\(|\b[A-Z][A-Z_]+/;
238
+
239
+ // Check if callback body only contains stable globals or simple operations
240
+ const hasOnlyStableGlobals = stableGlobals.some(pattern => pattern.test(callbackBody));
241
+ const hasImportedModule = importedModulePattern.test(callbackBody);
242
+
243
+ // Check if callback uses reactive values (props, state, context) that would need deps
244
+ const hasReactiveValues = /\b(props|state|context|use[A-Z]\w+\(\))/.test(callbackBody);
245
+
246
+ // Check for variable references that might be reactive
247
+ // Simple heuristic: if callback references variables that aren't stable globals
248
+ const variablePattern = /\b[a-z][a-zA-Z0-9_]*\b/g;
249
+ const variables = callbackBody.match(variablePattern) || [];
250
+ const hasNonGlobalVariables = variables.some(v => {
251
+ // Skip common stable globals and built-ins
252
+ const stableVars = ['window', 'document', 'localStorage', 'sessionStorage', 'console',
253
+ 'Math', 'Date', 'JSON', 'Object', 'Array', 'String', 'Number',
254
+ 'Boolean', 'RegExp', 'Error', 'Promise', 'Symbol', 'Reflect', 'Proxy',
255
+ 'true', 'false', 'null', 'undefined', 'this', 'return', 'if', 'else',
256
+ 'const', 'let', 'var', 'function', 'async', 'await', 'for', 'while',
257
+ 'switch', 'case', 'default', 'break', 'continue', 'try', 'catch', 'finally'];
258
+ return !stableVars.includes(v) && !v.match(/^[A-Z]/); // Lowercase vars that aren't stable
259
+ });
260
+
261
+ // Only warn if callback uses reactive values or non-global variables
262
+ if (hasReactiveValues || (hasNonGlobalVariables && !hasOnlyStableGlobals && !hasImportedModule)) {
263
+ warnings.push({
264
+ type: 'missing-dependencies',
265
+ file: relativePath,
266
+ line: getLineNumber(content, depMatch.index),
267
+ message: `${hookName} has empty dependency array but may need dependencies`,
268
+ recommendation: 'Review the hook body and add all dependencies to the dependency array. If the callback only uses stable global APIs (window, document, localStorage, etc.) or imported module-level constants, an empty dependency array is correct.'
269
+ });
270
+ }
271
+ }
272
+ }
273
+ });
274
+
275
+ // Check hook order (hooks should be called in consistent order)
276
+ // This is a simplified check - full implementation would track hook order across renders
277
+
278
+ // SKIP hook grouping suggestions for pace-core library files
279
+ // Library components often have complex hook usage organized by feature/concern,
280
+ // and strict grouping is less critical than in consuming applications.
281
+ // The grouping suggestions are primarily for consuming apps, not library code.
282
+ if (isPaceCorePackage) {
283
+ continue; // Skip grouping suggestions for pace-core library files
284
+ }
285
+
286
+ // Filter to only actual React hooks (not custom hooks) for grouping suggestions
287
+ // Files with only custom hooks (like useToast()) don't need grouping suggestions
288
+ const actualReactHookCalls = hookCalls.filter(h => actualReactHooks.includes(h.name));
289
+
290
+ // Skip files that only have custom hooks (no actual React hooks)
291
+ // Custom hooks are typically called once and don't need grouping
292
+ // This also filters out files like Toast.tsx that only have forwardRef components
293
+ // (forwardRef components don't use React hooks, so actualReactHookCalls will be empty)
294
+ if (actualReactHookCalls.length === 0) {
295
+ continue; // No actual React hooks, skip grouping suggestion
296
+ }
297
+
298
+ // Check if hooks are already grouped (look for grouping comments)
299
+ // Accept more flexible patterns:
300
+ // - Specific keywords: // ============================================================================ // REFS / STATE HOOKS / etc.
301
+ // - Section dividers: // ============================================================================ (any text)
302
+ // - Comment blocks near hooks: // HOOKS or // State management or similar
303
+ const hasGroupingComments =
304
+ /\/\/\s*=+\s*(REFS|STATE HOOKS|CUSTOM HOOKS|MEMOIZATION HOOKS|EFFECT HOOKS|HOOKS|STATE|EFFECTS|MEMOIZATION|CALLBACKS)/i.test(content) ||
305
+ /\/\/\s*=+\s*[A-Z].*HOOK/i.test(content) ||
306
+ /\/\/\s*=+\s*[A-Z].*STATE/i.test(content) ||
307
+ /\/\/\s*=+\s*[A-Z].*EFFECT/i.test(content);
308
+
309
+ // Only suggest grouping if:
310
+ // 1. File has actual React hooks (not just custom hooks)
311
+ // 2. Hooks are called multiple times (duplicate hook names)
312
+ // 3. Hooks are not already grouped (no grouping comments found)
313
+ const hookOrder = actualReactHookCalls.map(h => h.name);
314
+ const uniqueHooks = [...new Set(hookOrder)];
315
+
316
+ if (uniqueHooks.length !== hookOrder.length && !hasGroupingComments) {
317
+ // Hooks are called multiple times and not grouped
318
+ suggestions.push({
319
+ type: 'hook-order',
320
+ file: relativePath,
321
+ message: 'Consider grouping related hooks together for better readability',
322
+ recommendation: 'Group hooks by purpose (state hooks, effect hooks, custom hooks)'
323
+ });
324
+ }
325
+ } catch (error) {
326
+ // Skip files with errors
327
+ }
328
+ }
329
+
330
+ return { issues, warnings, suggestions };
331
+ }
332
+ };
333
+
334
+ module.exports = hooksCheck;
@@ -0,0 +1,244 @@
1
+ #!/usr/bin/env node
2
+
3
+ /**
4
+ * Import Patterns Check Module
5
+ * @package @jmruthers/pace-core
6
+ * @module Audit/Checks/Imports
7
+ *
8
+ * Checks for:
9
+ * - Unused imports from pace-core
10
+ * - Missing default imports
11
+ * - Circular dependencies
12
+ * - Wrong import paths
13
+ * - Barrel import anti-patterns
14
+ */
15
+
16
+ const fs = require('fs');
17
+ const path = require('path');
18
+ const { findSourceFiles, getRelativePath } = require('../utils.cjs');
19
+
20
+ const importsCheck = {
21
+ name: 'imports',
22
+ description: 'Import pattern analysis (unused imports, circular dependencies, wrong paths)',
23
+ severity: 'warning',
24
+
25
+ async run(context) {
26
+ const { projectRoot, files } = context;
27
+ const issues = [];
28
+ const warnings = [];
29
+ const suggestions = [];
30
+
31
+ if (!files || files.length === 0) {
32
+ return { issues, warnings, suggestions };
33
+ }
34
+
35
+ // Track imports and usage
36
+ const importMap = new Map(); // file -> { imports: [], exports: [] }
37
+ const usageMap = new Map(); // file -> Set of used identifiers
38
+
39
+ // First pass: collect imports and exports
40
+ for (const filePath of files) {
41
+ try {
42
+ const content = fs.readFileSync(filePath, 'utf8');
43
+ const relativePath = getRelativePath(filePath, projectRoot);
44
+
45
+ const imports = [];
46
+ const exports = [];
47
+ const used = new Set();
48
+
49
+ // Find all imports
50
+ const importPattern = /import\s+(?:(?:\*\s+as\s+(\w+))|(?:{([^}]+)})|(\w+))\s+from\s+['"]([^'"]+)['"]/g;
51
+ let match;
52
+ while ((match = importPattern.exec(content)) !== null) {
53
+ const namespace = match[1];
54
+ const namedImports = match[2];
55
+ const defaultImport = match[3];
56
+ const modulePath = match[4];
57
+
58
+ imports.push({
59
+ namespace,
60
+ namedImports: namedImports ? namedImports.split(',').map(s => s.trim().replace(/\s+as\s+\w+/, '')) : [],
61
+ defaultImport,
62
+ modulePath,
63
+ line: content.substring(0, match.index).split('\n').length
64
+ });
65
+ }
66
+
67
+ // Find all exports
68
+ const exportPattern = /export\s+(?:(?:default\s+)?(?:function|const|class|interface|type)\s+(\w+)|(?:{([^}]+)})|(\w+))/g;
69
+ while ((match = exportPattern.exec(content)) !== null) {
70
+ const namedExport = match[1] || match[3];
71
+ const namedExports = match[2];
72
+
73
+ if (namedExport) {
74
+ exports.push(namedExport);
75
+ }
76
+ if (namedExports) {
77
+ exports.push(...namedExports.split(',').map(s => s.trim()));
78
+ }
79
+ }
80
+
81
+ // Find usage of imported identifiers (simplified heuristic)
82
+ imports.forEach(imp => {
83
+ if (imp.defaultImport) {
84
+ // Check if default import is used
85
+ const usagePattern = new RegExp(`\\b${imp.defaultImport}\\b`, 'g');
86
+ if (usagePattern.test(content)) {
87
+ used.add(imp.defaultImport);
88
+ }
89
+ }
90
+ if (imp.namespace) {
91
+ const usagePattern = new RegExp(`\\b${imp.namespace}\\.`, 'g');
92
+ if (usagePattern.test(content)) {
93
+ used.add(imp.namespace);
94
+ }
95
+ }
96
+ imp.namedImports.forEach(name => {
97
+ const cleanName = name.trim();
98
+ const usagePattern = new RegExp(`\\b${cleanName}\\b`, 'g');
99
+ // Count occurrences - if more than 1, it's likely used (1 is the import itself)
100
+ const matches = content.match(usagePattern);
101
+ if (matches && matches.length > 1) {
102
+ used.add(cleanName);
103
+ }
104
+ });
105
+ });
106
+
107
+ importMap.set(relativePath, { imports, exports, content });
108
+ usageMap.set(relativePath, used);
109
+ } catch (error) {
110
+ // Skip files with errors
111
+ }
112
+ }
113
+
114
+ // Second pass: analyze imports
115
+ for (const [filePath, { imports, content }] of importMap.entries()) {
116
+ const relativePath = filePath;
117
+ const normalizedPath = relativePath.replace(/\\/g, '/');
118
+
119
+ // Skip pace-core package files - imports check is for consuming applications, not the library itself
120
+ // The library must use internal import paths and may have unused imports in examples/config files
121
+ const isPaceCorePackage = normalizedPath.includes('packages/core/') || normalizedPath.startsWith('packages/core/');
122
+ if (isPaceCorePackage) {
123
+ continue; // Skip library files
124
+ }
125
+
126
+ // Skip root-level src directory - in pace-core repository, this is a demo/showcase app
127
+ const isRootSrc = normalizedPath.startsWith('src/') && !normalizedPath.includes('packages/');
128
+ if (isRootSrc) {
129
+ continue; // Skip demo app files
130
+ }
131
+
132
+ // Skip scripts directory - utility scripts don't need import validation
133
+ const isScript = normalizedPath.startsWith('scripts/') || normalizedPath.includes('/scripts/');
134
+ if (isScript) {
135
+ continue; // Skip script files
136
+ }
137
+
138
+ const used = usageMap.get(filePath) || new Set();
139
+
140
+ imports.forEach(imp => {
141
+ // Check for unused imports from pace-core
142
+ if (imp.modulePath === '@jmruthers/pace-core' || imp.modulePath.startsWith('@jmruthers/pace-core/')) {
143
+ const unused = [];
144
+
145
+ if (imp.defaultImport && !used.has(imp.defaultImport)) {
146
+ unused.push(imp.defaultImport);
147
+ }
148
+
149
+ imp.namedImports.forEach(name => {
150
+ if (!used.has(name.trim())) {
151
+ unused.push(name.trim());
152
+ }
153
+ });
154
+
155
+ if (imp.namespace && !used.has(imp.namespace)) {
156
+ unused.push(`${imp.namespace} (namespace)`);
157
+ }
158
+
159
+ if (unused.length > 0) {
160
+ warnings.push({
161
+ type: 'unused-import',
162
+ file: relativePath,
163
+ line: imp.line,
164
+ message: `Unused imports from pace-core: ${unused.join(', ')}`,
165
+ recommendation: `Remove unused imports to reduce bundle size`
166
+ });
167
+ }
168
+ }
169
+
170
+ // Check for wrong import paths
171
+ if (imp.modulePath.startsWith('@jmruthers/pace-core/')) {
172
+ const validPaths = [
173
+ '@jmruthers/pace-core',
174
+ '@jmruthers/pace-core/components',
175
+ '@jmruthers/pace-core/hooks',
176
+ '@jmruthers/pace-core/utils',
177
+ '@jmruthers/pace-core/providers',
178
+ '@jmruthers/pace-core/rbac',
179
+ '@jmruthers/pace-core/types'
180
+ ];
181
+
182
+ if (!validPaths.includes(imp.modulePath)) {
183
+ warnings.push({
184
+ type: 'wrong-import-path',
185
+ file: relativePath,
186
+ line: imp.line,
187
+ message: `Invalid import path: ${imp.modulePath}`,
188
+ recommendation: `Use one of the valid paths: ${validPaths.join(', ')}`
189
+ });
190
+ }
191
+ }
192
+
193
+ // Check for barrel import anti-patterns (importing entire modules)
194
+ if (imp.namespace && (imp.modulePath === '@jmruthers/pace-core' || imp.modulePath.startsWith('@jmruthers/pace-core/'))) {
195
+ warnings.push({
196
+ type: 'barrel-import',
197
+ file: relativePath,
198
+ line: imp.line,
199
+ message: `Namespace import from pace-core: import * as ${imp.namespace}`,
200
+ recommendation: `Import specific exports instead to enable tree-shaking: import { Component1, Component2 } from '@jmruthers/pace-core'`
201
+ });
202
+ }
203
+ });
204
+ }
205
+
206
+ // Check for circular dependencies (simplified - check if file A imports B and B imports A)
207
+ const filePaths = Array.from(importMap.keys());
208
+ for (let i = 0; i < filePaths.length; i++) {
209
+ for (let j = i + 1; j < filePaths.length; j++) {
210
+ const fileA = filePaths[i];
211
+ const fileB = filePaths[j];
212
+ const importsA = importMap.get(fileA)?.imports || [];
213
+ const importsB = importMap.get(fileB)?.imports || [];
214
+
215
+ // Check if A imports B
216
+ const aImportsB = importsA.some(imp => {
217
+ const importPath = imp.modulePath;
218
+ return importPath.startsWith('./') || importPath.startsWith('../') ?
219
+ path.resolve(path.dirname(fileA), importPath) === fileB : false;
220
+ });
221
+
222
+ // Check if B imports A
223
+ const bImportsA = importsB.some(imp => {
224
+ const importPath = imp.modulePath;
225
+ return importPath.startsWith('./') || importPath.startsWith('../') ?
226
+ path.resolve(path.dirname(fileB), importPath) === fileA : false;
227
+ });
228
+
229
+ if (aImportsB && bImportsA) {
230
+ issues.push({
231
+ type: 'circular-dependency',
232
+ file: fileA,
233
+ message: `Circular dependency detected between ${fileA} and ${fileB}`,
234
+ recommendation: `Refactor to break the circular dependency by extracting shared code to a third module`
235
+ });
236
+ }
237
+ }
238
+ }
239
+
240
+ return { issues, warnings, suggestions };
241
+ }
242
+ };
243
+
244
+ module.exports = importsCheck;