@jmruthers/pace-core 0.5.193 → 0.6.2

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 (577) hide show
  1. package/CHANGELOG.md +62 -0
  2. package/README.md +7 -1
  3. package/cursor-rules/00-pace-core-compliance.mdc +299 -0
  4. package/cursor-rules/01-standards-compliance.mdc +244 -0
  5. package/cursor-rules/02-project-structure.mdc +200 -0
  6. package/cursor-rules/03-solid-principles.mdc +222 -0
  7. package/cursor-rules/04-testing-standards.mdc +268 -0
  8. package/cursor-rules/05-bug-reports-and-features.mdc +246 -0
  9. package/cursor-rules/06-code-quality.mdc +309 -0
  10. package/cursor-rules/07-tech-stack-compliance.mdc +214 -0
  11. package/cursor-rules/08-markup-quality.mdc +452 -0
  12. package/cursor-rules/CHANGELOG.md +119 -0
  13. package/cursor-rules/README.md +192 -0
  14. package/dist/{AuthService-DjnJHDtC.d.ts → AuthService-BPvc3Ka0.d.ts} +54 -0
  15. package/dist/{DataTable-Be6dH_dR.d.ts → DataTable-BMRU8a1j.d.ts} +34 -2
  16. package/dist/{DataTable-5FU7IESH.js → DataTable-TPTKCX4D.js} +10 -9
  17. package/dist/{PublicPageProvider-C0Sm_e5k.d.ts → PublicPageProvider-DC6kCaqf.d.ts} +385 -261
  18. package/dist/{UnifiedAuthProvider-RGJTDE2C.js → UnifiedAuthProvider-CH6Z342H.js} +3 -3
  19. package/dist/{UnifiedAuthProvider-185Ih4dj.d.ts → UnifiedAuthProvider-CVcTjx-d.d.ts} +29 -0
  20. package/dist/{api-N774RPUA.js → api-MVVQZLJI.js} +2 -2
  21. package/dist/{chunk-KNC55RTG.js → chunk-24UVZUZG.js} +90 -54
  22. package/dist/chunk-24UVZUZG.js.map +1 -0
  23. package/dist/{chunk-HWIIPPNI.js → chunk-2UOI2FG5.js} +20 -20
  24. package/dist/chunk-2UOI2FG5.js.map +1 -0
  25. package/dist/{chunk-E3SPN4VZ 5.js → chunk-3XC4CPTD.js} +4345 -3986
  26. package/dist/chunk-3XC4CPTD.js.map +1 -0
  27. package/dist/{chunk-7EQTDTTJ.js → chunk-6J4GEEJR.js} +172 -45
  28. package/dist/chunk-6J4GEEJR.js.map +1 -0
  29. package/dist/{chunk-6C4YBBJM 5.js → chunk-6SOIHG6Z.js} +1 -1
  30. package/dist/chunk-6SOIHG6Z.js.map +1 -0
  31. package/dist/{chunk-7FLMSG37.js → chunk-EHMR7VYL.js} +25 -25
  32. package/dist/chunk-EHMR7VYL.js.map +1 -0
  33. package/dist/{chunk-I7PSE6JW.js → chunk-F2IMUDXZ.js} +2 -75
  34. package/dist/chunk-F2IMUDXZ.js.map +1 -0
  35. package/dist/{chunk-QWWZ5CAQ.js → chunk-FFQEQTNW.js} +7 -9
  36. package/dist/chunk-FFQEQTNW.js.map +1 -0
  37. package/dist/chunk-FMUCXFII.js +76 -0
  38. package/dist/chunk-FMUCXFII.js.map +1 -0
  39. package/dist/{chunk-HW3OVDUF.js → chunk-J36DSWQK.js} +1 -1
  40. package/dist/{chunk-HW3OVDUF.js.map → chunk-J36DSWQK.js.map} +1 -1
  41. package/dist/{chunk-SQGMNID3.js → chunk-L4OXEN46.js} +4 -5
  42. package/dist/chunk-L4OXEN46.js.map +1 -0
  43. package/dist/{chunk-R77UEZ4E 3.js → chunk-M43Y4SSO.js} +1 -1
  44. package/dist/chunk-M43Y4SSO.js.map +1 -0
  45. package/dist/{chunk-IIELH4DL.js → chunk-MMZ7JXPU.js} +60 -223
  46. package/dist/chunk-MMZ7JXPU.js.map +1 -0
  47. package/dist/{chunk-NOAYCWCX 5.js → chunk-NECFR5MM.js} +394 -312
  48. package/dist/chunk-NECFR5MM.js.map +1 -0
  49. package/dist/{chunk-BC4IJKSL.js → chunk-SFZUDBL5.js} +40 -4
  50. package/dist/chunk-SFZUDBL5.js.map +1 -0
  51. package/dist/{chunk-XNXXZ43G.js → chunk-XWQCNGTQ.js} +748 -364
  52. package/dist/chunk-XWQCNGTQ.js.map +1 -0
  53. package/dist/components.d.ts +6 -6
  54. package/dist/components.js +15 -12
  55. package/dist/components.js.map +1 -1
  56. package/dist/{functions-D_kgHktt.d.ts → functions-DHebl8-F.d.ts} +1 -1
  57. package/dist/hooks.d.ts +59 -126
  58. package/dist/hooks.js +19 -28
  59. package/dist/hooks.js.map +1 -1
  60. package/dist/index.d.ts +63 -16
  61. package/dist/index.js +23 -24
  62. package/dist/index.js.map +1 -1
  63. package/dist/providers.d.ts +21 -3
  64. package/dist/providers.js +2 -2
  65. package/dist/rbac/index.d.ts +146 -115
  66. package/dist/rbac/index.js +8 -11
  67. package/dist/theming/runtime.d.ts +1 -13
  68. package/dist/theming/runtime.js +1 -1
  69. package/dist/{timezone-_pgH8qrY.d.ts → timezone-CHhWg6b4.d.ts} +3 -10
  70. package/dist/{types-UU913iLA.d.ts → types-BeoeWV5I.d.ts} +8 -0
  71. package/dist/{types-CEpcvwwF.d.ts → types-CkbwOr4Y.d.ts} +6 -0
  72. package/dist/types.d.ts +2 -2
  73. package/dist/{usePublicRouteParams-TZe0gy-4.d.ts → usePublicRouteParams-1oMokgLF.d.ts} +34 -4
  74. package/dist/{useToast-C8gR5ir4.d.ts → useToast-AyaT-x7p.d.ts} +2 -2
  75. package/dist/utils.d.ts +4 -5
  76. package/dist/utils.js +15 -15
  77. package/dist/utils.js.map +1 -1
  78. package/docs/api/README.md +7 -1
  79. package/docs/api/classes/ColumnFactory.md +8 -8
  80. package/docs/api/classes/InvalidScopeError.md +4 -4
  81. package/docs/api/classes/Logger.md +1 -1
  82. package/docs/api/classes/MissingUserContextError.md +4 -4
  83. package/docs/api/classes/OrganisationContextRequiredError.md +4 -4
  84. package/docs/api/classes/PermissionDeniedError.md +4 -4
  85. package/docs/api/classes/RBACAuditManager.md +1 -1
  86. package/docs/api/classes/RBACCache.md +1 -1
  87. package/docs/api/classes/RBACEngine.md +1 -1
  88. package/docs/api/classes/RBACError.md +4 -4
  89. package/docs/api/classes/RBACNotInitializedError.md +4 -4
  90. package/docs/api/classes/SecureSupabaseClient.md +18 -15
  91. package/docs/api/classes/StorageUtils.md +1 -1
  92. package/docs/api/enums/FileCategory.md +1 -1
  93. package/docs/api/enums/LogLevel.md +1 -1
  94. package/docs/api/enums/RBACErrorCode.md +1 -1
  95. package/docs/api/enums/RPCFunction.md +1 -1
  96. package/docs/api/interfaces/AddressFieldProps.md +1 -1
  97. package/docs/api/interfaces/AddressFieldRef.md +1 -1
  98. package/docs/api/interfaces/AggregateConfig.md +4 -4
  99. package/docs/api/interfaces/AutocompleteOptions.md +1 -1
  100. package/docs/api/interfaces/AvatarProps.md +1 -1
  101. package/docs/api/interfaces/BadgeProps.md +9 -2
  102. package/docs/api/interfaces/ButtonProps.md +7 -4
  103. package/docs/api/interfaces/CalendarProps.md +8 -5
  104. package/docs/api/interfaces/CardProps.md +8 -5
  105. package/docs/api/interfaces/ColorPalette.md +1 -1
  106. package/docs/api/interfaces/ColorShade.md +1 -1
  107. package/docs/api/interfaces/ComplianceResult.md +1 -1
  108. package/docs/api/interfaces/DataAccessRecord.md +9 -9
  109. package/docs/api/interfaces/DataRecord.md +1 -1
  110. package/docs/api/interfaces/DataTableAction.md +24 -21
  111. package/docs/api/interfaces/DataTableColumn.md +31 -31
  112. package/docs/api/interfaces/DataTableProps.md +1 -1
  113. package/docs/api/interfaces/DataTableToolbarButton.md +7 -7
  114. package/docs/api/interfaces/DatabaseComplianceResult.md +1 -1
  115. package/docs/api/interfaces/DatabaseIssue.md +1 -1
  116. package/docs/api/interfaces/EmptyStateConfig.md +5 -5
  117. package/docs/api/interfaces/EnhancedNavigationMenuProps.md +1 -1
  118. package/docs/api/interfaces/ErrorBoundaryProps.md +147 -0
  119. package/docs/api/interfaces/ErrorBoundaryProviderProps.md +36 -0
  120. package/docs/api/interfaces/ErrorBoundaryState.md +75 -0
  121. package/docs/api/interfaces/EventAppRoleData.md +1 -1
  122. package/docs/api/interfaces/ExportColumn.md +1 -1
  123. package/docs/api/interfaces/ExportOptions.md +8 -8
  124. package/docs/api/interfaces/FileDisplayProps.md +1 -1
  125. package/docs/api/interfaces/FileMetadata.md +1 -1
  126. package/docs/api/interfaces/FileReference.md +1 -1
  127. package/docs/api/interfaces/FileSizeLimits.md +1 -1
  128. package/docs/api/interfaces/FileUploadOptions.md +1 -1
  129. package/docs/api/interfaces/FileUploadProps.md +26 -23
  130. package/docs/api/interfaces/FooterProps.md +10 -8
  131. package/docs/api/interfaces/FormFieldProps.md +10 -10
  132. package/docs/api/interfaces/FormProps.md +1 -1
  133. package/docs/api/interfaces/GrantEventAppRoleParams.md +1 -1
  134. package/docs/api/interfaces/InactivityWarningModalProps.md +1 -1
  135. package/docs/api/interfaces/InputProps.md +7 -4
  136. package/docs/api/interfaces/LabelProps.md +1 -1
  137. package/docs/api/interfaces/LoggerConfig.md +1 -1
  138. package/docs/api/interfaces/LoginFormProps.md +14 -11
  139. package/docs/api/interfaces/NavigationAccessRecord.md +1 -1
  140. package/docs/api/interfaces/NavigationContextType.md +1 -1
  141. package/docs/api/interfaces/NavigationGuardProps.md +1 -1
  142. package/docs/api/interfaces/NavigationItem.md +11 -11
  143. package/docs/api/interfaces/NavigationMenuProps.md +15 -15
  144. package/docs/api/interfaces/NavigationProviderProps.md +1 -1
  145. package/docs/api/interfaces/Organisation.md +1 -1
  146. package/docs/api/interfaces/OrganisationContextType.md +1 -1
  147. package/docs/api/interfaces/OrganisationMembership.md +1 -1
  148. package/docs/api/interfaces/OrganisationProviderProps.md +1 -1
  149. package/docs/api/interfaces/OrganisationSecurityError.md +1 -1
  150. package/docs/api/interfaces/PaceAppLayoutProps.md +30 -27
  151. package/docs/api/interfaces/PaceLoginPageProps.md +6 -4
  152. package/docs/api/interfaces/PageAccessRecord.md +1 -1
  153. package/docs/api/interfaces/PagePermissionContextType.md +1 -1
  154. package/docs/api/interfaces/PagePermissionGuardProps.md +1 -1
  155. package/docs/api/interfaces/PagePermissionProviderProps.md +1 -1
  156. package/docs/api/interfaces/PaletteData.md +1 -1
  157. package/docs/api/interfaces/ParsedAddress.md +1 -1
  158. package/docs/api/interfaces/PermissionEnforcerProps.md +1 -1
  159. package/docs/api/interfaces/ProgressProps.md +1 -1
  160. package/docs/api/interfaces/ProtectedRouteProps.md +7 -26
  161. package/docs/api/interfaces/PublicPageFooterProps.md +9 -9
  162. package/docs/api/interfaces/PublicPageHeaderProps.md +10 -10
  163. package/docs/api/interfaces/PublicPageLayoutProps.md +7 -20
  164. package/docs/api/interfaces/QuickFix.md +1 -1
  165. package/docs/api/interfaces/RBACAccessValidateParams.md +1 -1
  166. package/docs/api/interfaces/RBACAccessValidateResult.md +1 -1
  167. package/docs/api/interfaces/RBACAuditLogParams.md +1 -1
  168. package/docs/api/interfaces/RBACAuditLogResult.md +1 -1
  169. package/docs/api/interfaces/RBACConfig.md +1 -1
  170. package/docs/api/interfaces/RBACContext.md +1 -1
  171. package/docs/api/interfaces/RBACLogger.md +1 -1
  172. package/docs/api/interfaces/RBACPageAccessCheckParams.md +1 -1
  173. package/docs/api/interfaces/RBACPerformanceMetrics.md +1 -1
  174. package/docs/api/interfaces/RBACPermissionCheckParams.md +1 -1
  175. package/docs/api/interfaces/RBACPermissionCheckResult.md +1 -1
  176. package/docs/api/interfaces/RBACPermissionsGetParams.md +1 -1
  177. package/docs/api/interfaces/RBACPermissionsGetResult.md +1 -1
  178. package/docs/api/interfaces/RBACResult.md +1 -1
  179. package/docs/api/interfaces/RBACRoleGrantParams.md +1 -1
  180. package/docs/api/interfaces/RBACRoleGrantResult.md +1 -1
  181. package/docs/api/interfaces/RBACRoleRevokeParams.md +1 -1
  182. package/docs/api/interfaces/RBACRoleRevokeResult.md +1 -1
  183. package/docs/api/interfaces/RBACRoleValidateParams.md +1 -1
  184. package/docs/api/interfaces/RBACRoleValidateResult.md +1 -1
  185. package/docs/api/interfaces/RBACRolesListParams.md +1 -1
  186. package/docs/api/interfaces/RBACRolesListResult.md +1 -1
  187. package/docs/api/interfaces/RBACSessionTrackParams.md +1 -1
  188. package/docs/api/interfaces/RBACSessionTrackResult.md +1 -1
  189. package/docs/api/interfaces/ResourcePermissions.md +1 -1
  190. package/docs/api/interfaces/RevokeEventAppRoleParams.md +1 -1
  191. package/docs/api/interfaces/RoleBasedRouterContextType.md +1 -1
  192. package/docs/api/interfaces/RoleBasedRouterProps.md +1 -1
  193. package/docs/api/interfaces/RoleManagementResult.md +1 -1
  194. package/docs/api/interfaces/RouteAccessRecord.md +1 -1
  195. package/docs/api/interfaces/RouteConfig.md +1 -1
  196. package/docs/api/interfaces/RuntimeComplianceResult.md +1 -1
  197. package/docs/api/interfaces/SecureDataContextType.md +9 -9
  198. package/docs/api/interfaces/SecureDataProviderProps.md +8 -8
  199. package/docs/api/interfaces/SessionRestorationLoaderProps.md +3 -3
  200. package/docs/api/interfaces/SetupIssue.md +1 -1
  201. package/docs/api/interfaces/StorageConfig.md +1 -1
  202. package/docs/api/interfaces/StorageFileInfo.md +1 -1
  203. package/docs/api/interfaces/StorageFileMetadata.md +1 -1
  204. package/docs/api/interfaces/StorageListOptions.md +1 -1
  205. package/docs/api/interfaces/StorageListResult.md +1 -1
  206. package/docs/api/interfaces/StorageUploadOptions.md +1 -1
  207. package/docs/api/interfaces/StorageUploadResult.md +1 -1
  208. package/docs/api/interfaces/StorageUrlOptions.md +1 -1
  209. package/docs/api/interfaces/StyleImport.md +1 -1
  210. package/docs/api/interfaces/SwitchProps.md +1 -1
  211. package/docs/api/interfaces/TabsContentProps.md +1 -1
  212. package/docs/api/interfaces/TabsListProps.md +1 -1
  213. package/docs/api/interfaces/TabsProps.md +1 -1
  214. package/docs/api/interfaces/TabsTriggerProps.md +3 -3
  215. package/docs/api/interfaces/TextareaProps.md +1 -1
  216. package/docs/api/interfaces/ToastActionElement.md +4 -1
  217. package/docs/api/interfaces/ToastProps.md +1 -1
  218. package/docs/api/interfaces/UnifiedAuthContextType.md +58 -55
  219. package/docs/api/interfaces/UnifiedAuthProviderProps.md +15 -13
  220. package/docs/api/interfaces/UseFormDialogOptions.md +1 -1
  221. package/docs/api/interfaces/UseFormDialogReturn.md +1 -1
  222. package/docs/api/interfaces/UseInactivityTrackerOptions.md +11 -9
  223. package/docs/api/interfaces/UseInactivityTrackerReturn.md +8 -8
  224. package/docs/api/interfaces/UsePublicEventLogoOptions.md +6 -6
  225. package/docs/api/interfaces/UsePublicEventLogoReturn.md +9 -6
  226. package/docs/api/interfaces/UsePublicEventOptions.md +3 -3
  227. package/docs/api/interfaces/UsePublicEventReturn.md +8 -5
  228. package/docs/api/interfaces/UsePublicFileDisplayOptions.md +4 -4
  229. package/docs/api/interfaces/UsePublicFileDisplayReturn.md +12 -9
  230. package/docs/api/interfaces/UsePublicRouteParamsReturn.md +10 -7
  231. package/docs/api/interfaces/UseResolvedScopeOptions.md +1 -1
  232. package/docs/api/interfaces/UseResolvedScopeReturn.md +1 -1
  233. package/docs/api/interfaces/UseResourcePermissionsOptions.md +1 -1
  234. package/docs/api/interfaces/UserEventAccess.md +14 -11
  235. package/docs/api/interfaces/UserMenuProps.md +8 -6
  236. package/docs/api/interfaces/UserProfile.md +1 -1
  237. package/docs/api/modules.md +575 -634
  238. package/docs/architecture/database-schema-requirements.md +161 -0
  239. package/docs/core-concepts/rbac-system.md +3 -3
  240. package/docs/documentation-index.md +2 -4
  241. package/docs/getting-started/cursor-rules.md +263 -0
  242. package/docs/getting-started/installation-guide.md +6 -1
  243. package/docs/getting-started/quick-start.md +6 -1
  244. package/docs/migration/DOCUMENTATION_STRUCTURE.md +441 -0
  245. package/docs/migration/MIGRATION_GUIDE.md +6 -28
  246. package/docs/migration/README.md +52 -6
  247. package/docs/migration/V0.5.190_TO_V0.6.1_MIGRATION.md +1153 -0
  248. package/docs/migration/V0.6.0_REACT_19_MIGRATION.md +227 -0
  249. package/docs/migration/database-changes-december-2025.md +3 -3
  250. package/docs/rbac/event-based-apps.md +1 -1
  251. package/docs/rbac/getting-started.md +1 -1
  252. package/docs/rbac/quick-start.md +1 -1
  253. package/docs/standards/README.md +40 -0
  254. package/docs/troubleshooting/migration.md +4 -4
  255. package/examples/PublicPages/PublicEventPage.tsx +1 -1
  256. package/package.json +12 -6
  257. package/scripts/audit/core/checks/accessibility.cjs +197 -0
  258. package/scripts/audit/core/checks/api-usage.cjs +191 -0
  259. package/scripts/audit/core/checks/bundle.cjs +142 -0
  260. package/scripts/{check-pace-core-compliance.cjs → audit/core/checks/compliance.cjs} +737 -691
  261. package/scripts/audit/core/checks/config.cjs +54 -0
  262. package/scripts/audit/core/checks/coverage.cjs +84 -0
  263. package/scripts/audit/core/checks/dependencies.cjs +454 -0
  264. package/scripts/audit/core/checks/documentation.cjs +203 -0
  265. package/scripts/audit/core/checks/environment.cjs +128 -0
  266. package/scripts/audit/core/checks/error-handling.cjs +299 -0
  267. package/scripts/audit/core/checks/forms.cjs +172 -0
  268. package/scripts/audit/core/checks/heuristics.cjs +68 -0
  269. package/scripts/audit/core/checks/hooks.cjs +334 -0
  270. package/scripts/audit/core/checks/imports.cjs +244 -0
  271. package/scripts/audit/core/checks/performance.cjs +325 -0
  272. package/scripts/audit/core/checks/routes.cjs +117 -0
  273. package/scripts/audit/core/checks/state.cjs +130 -0
  274. package/scripts/audit/core/checks/structure.cjs +65 -0
  275. package/scripts/audit/core/checks/style.cjs +584 -0
  276. package/scripts/audit/core/checks/testing.cjs +122 -0
  277. package/scripts/audit/core/checks/typescript.cjs +61 -0
  278. package/scripts/audit/core/scanner.cjs +199 -0
  279. package/scripts/audit/core/utils.cjs +137 -0
  280. package/scripts/audit/index.cjs +223 -0
  281. package/scripts/audit/reporters/console.cjs +151 -0
  282. package/scripts/audit/reporters/json.cjs +54 -0
  283. package/scripts/audit/reporters/markdown.cjs +124 -0
  284. package/scripts/audit-consuming-app.cjs +86 -0
  285. package/scripts/build-docs/build-decision.js +240 -0
  286. package/scripts/build-docs/cache-utils.js +105 -0
  287. package/scripts/build-docs/content-normalization.js +150 -0
  288. package/scripts/build-docs/file-utils.js +105 -0
  289. package/scripts/build-docs/git-utils.js +86 -0
  290. package/scripts/build-docs/hash-utils.js +116 -0
  291. package/scripts/build-docs/typedoc-runner.js +220 -0
  292. package/scripts/build-docs-incremental.js +77 -913
  293. package/scripts/install-cursor-rules.cjs +236 -0
  294. package/scripts/utils/command-runner.js +16 -11
  295. package/scripts/validate-formats.js +61 -56
  296. package/scripts/validate-master.js +74 -69
  297. package/scripts/validate-pre-publish.js +70 -65
  298. package/src/__tests__/helpers/test-providers.tsx +1 -1
  299. package/src/__tests__/helpers/test-utils.tsx +1 -1
  300. package/src/__tests__/hooks/usePermissions.test.ts +2 -2
  301. package/src/components/Alert/Alert.test.tsx +12 -18
  302. package/src/components/Alert/Alert.tsx +5 -7
  303. package/src/components/Avatar/Avatar.test.tsx +4 -4
  304. package/src/components/Badge/Badge.tsx +16 -4
  305. package/src/components/Button/Button.tsx +27 -4
  306. package/src/components/Calendar/Calendar.tsx +9 -3
  307. package/src/components/Card/Card.tsx +4 -0
  308. package/src/components/Checkbox/Checkbox.test.tsx +12 -12
  309. package/src/components/Checkbox/Checkbox.tsx +2 -2
  310. package/src/components/DataTable/DataTable.test.tsx +57 -93
  311. package/src/components/DataTable/DataTable.tsx +40 -6
  312. package/src/components/DataTable/__tests__/DataTableCore.test-setup.ts +5 -6
  313. package/src/components/DataTable/__tests__/pagination.modes.test.tsx +29 -7
  314. package/src/components/DataTable/__tests__/ssr.strict-mode.test.tsx +12 -12
  315. package/src/components/DataTable/__tests__/test-utils/sharedTestUtils.tsx +2 -3
  316. package/src/components/DataTable/components/AccessDeniedPage.tsx +17 -26
  317. package/src/components/DataTable/components/ActionButtons.tsx +10 -7
  318. package/src/components/DataTable/components/BulkOperationsDropdown.tsx +2 -2
  319. package/src/components/DataTable/components/ColumnFilter.tsx +10 -0
  320. package/src/components/DataTable/components/ColumnVisibilityDropdown.tsx +12 -0
  321. package/src/components/DataTable/components/DataTableBody.tsx +8 -0
  322. package/src/components/DataTable/components/DataTableCore.tsx +200 -561
  323. package/src/components/DataTable/components/DataTableErrorBoundary.tsx +11 -0
  324. package/src/components/DataTable/components/DataTableLayout.tsx +559 -0
  325. package/src/components/DataTable/components/DataTableModals.tsx +9 -1
  326. package/src/components/DataTable/components/DataTableToolbar.tsx +8 -0
  327. package/src/components/DataTable/components/DraggableColumnHeader.tsx +12 -0
  328. package/src/components/DataTable/components/EditFields.tsx +307 -0
  329. package/src/components/DataTable/components/EditableRow.tsx +9 -1
  330. package/src/components/DataTable/components/EmptyState.tsx +10 -0
  331. package/src/components/DataTable/components/FilterRow.tsx +12 -0
  332. package/src/components/DataTable/components/GroupHeader.tsx +12 -0
  333. package/src/components/DataTable/components/GroupingDropdown.tsx +12 -0
  334. package/src/components/DataTable/components/ImportModal.tsx +7 -0
  335. package/src/components/DataTable/components/LoadingState.tsx +6 -0
  336. package/src/components/DataTable/components/PaginationControls.tsx +16 -1
  337. package/src/components/DataTable/components/RowComponent.tsx +391 -0
  338. package/src/components/DataTable/components/UnifiedTableBody.tsx +62 -852
  339. package/src/components/DataTable/components/VirtualizedDataTable.tsx +16 -4
  340. package/src/components/DataTable/components/__tests__/AccessDeniedPage.test.tsx +4 -2
  341. package/src/components/DataTable/components/__tests__/DataTableModals.test.tsx +23 -23
  342. package/src/components/DataTable/components/__tests__/EditableRow.test.tsx +11 -11
  343. package/src/components/DataTable/components/__tests__/ExpandButton.test.tsx +36 -36
  344. package/src/components/DataTable/components/__tests__/GroupHeader.test.tsx +27 -27
  345. package/src/components/DataTable/components/__tests__/ImportModal.test.tsx +39 -39
  346. package/src/components/DataTable/components/__tests__/UnifiedTableBody.test.tsx +33 -33
  347. package/src/components/DataTable/components/__tests__/ViewRowModal.test.tsx +29 -29
  348. package/src/components/DataTable/components/cellValueUtils.ts +40 -0
  349. package/src/components/DataTable/components/hooks/useImportModalFocus.ts +53 -0
  350. package/src/components/DataTable/components/hooks/usePermissionTracking.ts +126 -0
  351. package/src/components/DataTable/context/DataTableContext.tsx +50 -0
  352. package/src/components/DataTable/core/ColumnFactory.ts +31 -0
  353. package/src/components/DataTable/core/DataTableContext.tsx +32 -1
  354. package/src/components/DataTable/hooks/useColumnOrderPersistence.ts +10 -0
  355. package/src/components/DataTable/hooks/useColumnReordering.ts +14 -2
  356. package/src/components/DataTable/hooks/useColumnVisibilityPersistence.ts +10 -0
  357. package/src/components/DataTable/hooks/useDataTableDataPipeline.ts +16 -0
  358. package/src/components/DataTable/hooks/useDataTablePermissions.ts +124 -32
  359. package/src/components/DataTable/hooks/useDataTableState.ts +35 -1
  360. package/src/components/DataTable/hooks/useEffectiveColumnOrder.ts +12 -0
  361. package/src/components/DataTable/hooks/useKeyboardNavigation.ts +2 -2
  362. package/src/components/DataTable/hooks/useServerSideDataEffect.ts +11 -0
  363. package/src/components/DataTable/hooks/useTableColumns.ts +8 -0
  364. package/src/components/DataTable/hooks/useTableHandlers.ts +14 -0
  365. package/src/components/DataTable/styles.ts +6 -6
  366. package/src/components/DataTable/types.ts +6 -10
  367. package/src/components/DataTable/utils/a11yUtils.ts +7 -0
  368. package/src/components/DataTable/utils/debugTools.ts +18 -113
  369. package/src/components/DataTable/utils/errorHandling.ts +12 -0
  370. package/src/components/DataTable/utils/exportUtils.ts +9 -0
  371. package/src/components/DataTable/utils/flexibleImport.ts +12 -48
  372. package/src/components/DataTable/utils/paginationUtils.ts +8 -0
  373. package/src/components/DataTable/utils/performanceUtils.ts +5 -1
  374. package/src/components/DatePickerWithTimezone/DatePickerWithTimezone.test.tsx +8 -14
  375. package/src/components/Dialog/Dialog.tsx +8 -7
  376. package/src/components/ErrorBoundary/ErrorBoundary.test.tsx +180 -1
  377. package/src/components/ErrorBoundary/ErrorBoundary.tsx +46 -6
  378. package/src/components/ErrorBoundary/ErrorBoundaryContext.tsx +129 -0
  379. package/src/components/ErrorBoundary/index.ts +27 -2
  380. package/src/components/EventSelector/EventSelector.tsx +4 -1
  381. package/src/components/FileDisplay/FileDisplay.test.tsx +2 -2
  382. package/src/components/FileDisplay/FileDisplay.tsx +32 -18
  383. package/src/components/FileUpload/FileUpload.tsx +22 -2
  384. package/src/components/Footer/Footer.test.tsx +16 -16
  385. package/src/components/Footer/Footer.tsx +15 -12
  386. package/src/components/Form/Form.test.tsx +36 -15
  387. package/src/components/Form/Form.tsx +31 -26
  388. package/src/components/Header/Header.tsx +22 -11
  389. package/src/components/InactivityWarningModal/InactivityWarningModal.test.tsx +40 -40
  390. package/src/components/InactivityWarningModal/InactivityWarningModal.tsx +1 -1
  391. package/src/components/Input/Input.test.tsx +2 -2
  392. package/src/components/Input/Input.tsx +36 -34
  393. package/src/components/Label/Label.tsx +1 -1
  394. package/src/components/LoadingSpinner/LoadingSpinner.test.tsx +4 -4
  395. package/src/components/LoadingSpinner/LoadingSpinner.tsx +1 -1
  396. package/src/components/LoginForm/LoginForm.test.tsx +42 -42
  397. package/src/components/LoginForm/LoginForm.tsx +12 -8
  398. package/src/components/NavigationMenu/NavigationMenu.tsx +15 -514
  399. package/src/components/NavigationMenu/types.ts +56 -0
  400. package/src/components/NavigationMenu/useNavigationFiltering.ts +390 -0
  401. package/src/components/OrganisationSelector/OrganisationSelector.tsx +3 -0
  402. package/src/components/PaceAppLayout/PaceAppLayout.performance.test.tsx +1 -1
  403. package/src/components/PaceAppLayout/PaceAppLayout.test.tsx +54 -52
  404. package/src/components/PaceAppLayout/PaceAppLayout.tsx +33 -12
  405. package/src/components/PaceAppLayout/README.md +1 -1
  406. package/src/components/PaceAppLayout/test-setup.tsx +1 -2
  407. package/src/components/PaceLoginPage/PaceLoginPage.tsx +4 -1
  408. package/src/components/PasswordChange/PasswordChangeForm.test.tsx +33 -33
  409. package/src/components/PasswordChange/PasswordChangeForm.tsx +10 -1
  410. package/src/components/Progress/Progress.tsx +1 -1
  411. package/src/components/ProtectedRoute/ProtectedRoute.tsx +3 -9
  412. package/src/components/PublicLayout/PublicPageLayout.tsx +3 -6
  413. package/src/components/PublicLayout/PublicPageProvider.tsx +4 -0
  414. package/src/components/Select/Select.tsx +95 -438
  415. package/src/components/Select/context.ts +23 -0
  416. package/src/components/Select/hooks/useSelectEvents.ts +87 -0
  417. package/src/components/Select/hooks/useSelectSearch.ts +91 -0
  418. package/src/components/Select/hooks/useSelectState.ts +104 -0
  419. package/src/components/Select/index.ts +9 -1
  420. package/src/components/Select/types.ts +123 -0
  421. package/src/components/Select/utils/text.ts +26 -0
  422. package/src/components/SessionRestorationLoader/SessionRestorationLoader.tsx +5 -6
  423. package/src/components/Switch/Switch.tsx +4 -4
  424. package/src/components/Table/Table.tsx +1 -1
  425. package/src/components/Tabs/Tabs.tsx +1 -1
  426. package/src/components/Textarea/Textarea.tsx +27 -29
  427. package/src/components/Toast/Toast.tsx +5 -1
  428. package/src/components/Tooltip/Tooltip.tsx +3 -3
  429. package/src/components/UserMenu/UserMenu.test.tsx +24 -11
  430. package/src/components/UserMenu/UserMenu.tsx +22 -19
  431. package/src/components/index.ts +2 -2
  432. package/src/hooks/__tests__/hooks.integration.test.tsx +80 -55
  433. package/src/hooks/__tests__/index.unit.test.ts +2 -5
  434. package/src/hooks/__tests__/useStorage.unit.test.ts +36 -36
  435. package/src/hooks/index.ts +1 -2
  436. package/src/hooks/public/usePublicEvent.ts +5 -1
  437. package/src/hooks/public/usePublicEventLogo.ts +5 -1
  438. package/src/hooks/public/usePublicFileDisplay.ts +4 -0
  439. package/src/hooks/public/usePublicRouteParams.ts +5 -1
  440. package/src/hooks/services/useAuth.ts +32 -0
  441. package/src/hooks/services/useCurrentEvent.ts +6 -0
  442. package/src/hooks/services/useCurrentOrganisation.ts +6 -0
  443. package/src/hooks/useDataTableState.ts +8 -18
  444. package/src/hooks/useDebounce.ts +9 -0
  445. package/src/hooks/useEventTheme.ts +6 -0
  446. package/src/hooks/useFileDisplay.ts +4 -0
  447. package/src/hooks/useFileReference.ts +25 -7
  448. package/src/hooks/useFileUrl.ts +11 -1
  449. package/src/hooks/useFocusManagement.ts +16 -2
  450. package/src/hooks/useFocusTrap.ts +7 -4
  451. package/src/hooks/useFormDialog.ts +8 -7
  452. package/src/hooks/useInactivityTracker.ts +4 -1
  453. package/src/hooks/useKeyboardShortcuts.ts +4 -0
  454. package/src/hooks/useOrganisationPermissions.ts +4 -0
  455. package/src/hooks/useOrganisationSecurity.ts +4 -0
  456. package/src/hooks/usePerformanceMonitor.ts +4 -0
  457. package/src/hooks/usePermissionCache.ts +8 -1
  458. package/src/hooks/useQueryCache.ts +12 -1
  459. package/src/hooks/useSessionRestoration.ts +4 -0
  460. package/src/hooks/useStorage.ts +4 -0
  461. package/src/hooks/useToast.ts +3 -3
  462. package/src/index.ts +2 -1
  463. package/src/providers/__tests__/OrganisationProvider.test.tsx +115 -49
  464. package/src/providers/__tests__/ProviderLifecycle.test.tsx +21 -6
  465. package/src/providers/__tests__/UnifiedAuthProvider.test.tsx +10 -10
  466. package/src/providers/services/AuthServiceProvider.tsx +18 -0
  467. package/src/providers/services/EventServiceProvider.tsx +18 -0
  468. package/src/providers/services/InactivityServiceProvider.tsx +18 -0
  469. package/src/providers/services/OrganisationServiceProvider.tsx +18 -0
  470. package/src/providers/services/UnifiedAuthProvider.tsx +58 -22
  471. package/src/providers/services/__tests__/AuthServiceProvider.integration.test.tsx +33 -7
  472. package/src/rbac/README.md +1 -1
  473. package/src/rbac/__tests__/adapters.comprehensive.test.tsx +26 -26
  474. package/src/rbac/__tests__/scenarios.user-role.test.tsx +4 -5
  475. package/src/rbac/adapters.tsx +14 -5
  476. package/src/rbac/api.ts +100 -67
  477. package/src/rbac/components/EnhancedNavigationMenu.tsx +1 -1
  478. package/src/rbac/components/NavigationGuard.tsx +1 -1
  479. package/src/rbac/components/NavigationProvider.tsx +5 -2
  480. package/src/rbac/components/PagePermissionGuard.tsx +158 -18
  481. package/src/rbac/components/PagePermissionProvider.tsx +1 -1
  482. package/src/rbac/components/PermissionEnforcer.tsx +1 -1
  483. package/src/rbac/components/RoleBasedRouter.tsx +6 -2
  484. package/src/rbac/components/SecureDataProvider.test.tsx +84 -49
  485. package/src/rbac/components/SecureDataProvider.tsx +21 -6
  486. package/src/rbac/components/__tests__/PagePermissionGuard.race-condition.test.tsx +24 -14
  487. package/src/rbac/components/__tests__/PagePermissionGuard.test.tsx +7 -0
  488. package/src/rbac/components/__tests__/PagePermissionGuard.verification.test.tsx +14 -6
  489. package/src/rbac/components/__tests__/RoleBasedRouter.test.tsx +15 -4
  490. package/src/rbac/components/__tests__/SecureDataProvider.fixed.test.tsx +148 -24
  491. package/src/rbac/components/__tests__/SecureDataProvider.test.tsx +81 -15
  492. package/src/rbac/engine.ts +38 -14
  493. package/src/rbac/hooks/permissions/index.ts +7 -0
  494. package/src/rbac/hooks/permissions/useAccessLevel.ts +105 -0
  495. package/src/rbac/hooks/permissions/useCachedPermissions.ts +79 -0
  496. package/src/rbac/hooks/permissions/useCan.ts +347 -0
  497. package/src/rbac/hooks/permissions/useHasAllPermissions.ts +90 -0
  498. package/src/rbac/hooks/permissions/useHasAnyPermission.ts +90 -0
  499. package/src/rbac/hooks/permissions/useMultiplePermissions.ts +93 -0
  500. package/src/rbac/hooks/permissions/usePermissions.ts +253 -0
  501. package/src/rbac/hooks/useCan.test.ts +71 -64
  502. package/src/rbac/hooks/usePermissions.ts +14 -995
  503. package/src/rbac/hooks/useResourcePermissions.test.ts +54 -18
  504. package/src/rbac/hooks/useResourcePermissions.ts +14 -4
  505. package/src/rbac/hooks/useSecureSupabase.ts +33 -13
  506. package/src/rbac/permissions.ts +0 -30
  507. package/src/rbac/secureClient.ts +212 -61
  508. package/src/rbac/types.ts +8 -0
  509. package/src/theming/__tests__/parseEventColours.test.ts +6 -9
  510. package/src/theming/parseEventColours.ts +5 -19
  511. package/src/types/vitest-globals.d.ts +51 -26
  512. package/src/utils/__mocks__/supabaseMock.ts +1 -3
  513. package/src/utils/__tests__/formatting.unit.test.ts +4 -4
  514. package/src/utils/__tests__/index.unit.test.ts +2 -2
  515. package/src/utils/audit/audit.ts +0 -3
  516. package/src/utils/core/cn.ts +1 -1
  517. package/src/utils/file-reference/index.ts +53 -1
  518. package/src/utils/formatting/formatting.ts +8 -18
  519. package/src/utils/index.ts +0 -1
  520. package/src/utils/security/secureDataAccess.test.ts +31 -20
  521. package/src/utils/security/secureDataAccess.ts +4 -3
  522. package/dist/chunk-6C4YBBJM.js +0 -628
  523. package/dist/chunk-6C4YBBJM.js.map +0 -1
  524. package/dist/chunk-7D4SUZUM.js 2.map +0 -1
  525. package/dist/chunk-7EQTDTTJ.js 2.map +0 -1
  526. package/dist/chunk-7EQTDTTJ.js.map +0 -1
  527. package/dist/chunk-7FLMSG37.js 2.map +0 -1
  528. package/dist/chunk-7FLMSG37.js.map +0 -1
  529. package/dist/chunk-BC4IJKSL.js.map +0 -1
  530. package/dist/chunk-E3SPN4VZ.js +0 -12917
  531. package/dist/chunk-E3SPN4VZ.js.map +0 -1
  532. package/dist/chunk-E66EQZE6 5.js +0 -37
  533. package/dist/chunk-E66EQZE6.js 2.map +0 -1
  534. package/dist/chunk-HWIIPPNI.js.map +0 -1
  535. package/dist/chunk-I7PSE6JW 5.js +0 -191
  536. package/dist/chunk-I7PSE6JW.js 2.map +0 -1
  537. package/dist/chunk-I7PSE6JW.js.map +0 -1
  538. package/dist/chunk-IIELH4DL.js.map +0 -1
  539. package/dist/chunk-KNC55RTG.js 5.map +0 -1
  540. package/dist/chunk-KNC55RTG.js.map +0 -1
  541. package/dist/chunk-KQCRWDSA.js 5.map +0 -1
  542. package/dist/chunk-LFNCN2SP.js +0 -412
  543. package/dist/chunk-LFNCN2SP.js 2.map +0 -1
  544. package/dist/chunk-LFNCN2SP.js.map +0 -1
  545. package/dist/chunk-LMC26NLJ 2.js +0 -84
  546. package/dist/chunk-NOAYCWCX.js +0 -4993
  547. package/dist/chunk-NOAYCWCX.js.map +0 -1
  548. package/dist/chunk-QWWZ5CAQ.js 3.map +0 -1
  549. package/dist/chunk-QWWZ5CAQ.js.map +0 -1
  550. package/dist/chunk-QXHPKYJV 3.js +0 -113
  551. package/dist/chunk-R77UEZ4E.js +0 -68
  552. package/dist/chunk-R77UEZ4E.js.map +0 -1
  553. package/dist/chunk-SQGMNID3.js.map +0 -1
  554. package/dist/chunk-VBXEHIUJ.js 6.map +0 -1
  555. package/dist/chunk-XNXXZ43G.js.map +0 -1
  556. package/dist/chunk-ZSAAAMVR 6.js +0 -25
  557. package/dist/components.js 5.map +0 -1
  558. package/dist/styles/index 2.js +0 -12
  559. package/dist/styles/index.js 5.map +0 -1
  560. package/dist/theming/runtime 5.js +0 -19
  561. package/dist/theming/runtime.js 5.map +0 -1
  562. package/docs/api/classes/ErrorBoundary.md +0 -144
  563. package/docs/migration/quick-migration-guide.md +0 -356
  564. package/docs/migration/service-architecture.md +0 -281
  565. package/src/hooks/__tests__/useSecureDataAccess.unit.test.tsx +0 -680
  566. package/src/hooks/useSecureDataAccess.test.ts +0 -559
  567. package/src/hooks/useSecureDataAccess.ts +0 -666
  568. /package/dist/{DataTable-5FU7IESH.js.map → DataTable-TPTKCX4D.js.map} +0 -0
  569. /package/dist/{UnifiedAuthProvider-RGJTDE2C.js.map → UnifiedAuthProvider-CH6Z342H.js.map} +0 -0
  570. /package/dist/{api-N774RPUA.js.map → api-MVVQZLJI.js.map} +0 -0
  571. /package/docs/migration/{organisation-context-timing-fix.md → V0.3.44_organisation-context-timing-fix.md} +0 -0
  572. /package/docs/migration/{rbac-migration.md → V0.4.0_rbac-migration.md} +0 -0
  573. /package/docs/migration/{person-scoped-profiles-migration-guide.md → V0.5.190_person-scoped-profiles-migration-guide.md} +0 -0
  574. /package/examples/{rbac → RBAC}/CompleteRBACExample.tsx +0 -0
  575. /package/examples/{rbac → RBAC}/EventBasedApp.tsx +0 -0
  576. /package/examples/{rbac → RBAC}/PermissionExample.tsx +0 -0
  577. /package/examples/{rbac → RBAC}/index.ts +0 -0
@@ -1,51 +1,33 @@
1
1
  #!/usr/bin/env node
2
2
 
3
3
  /**
4
- * Static Analysis Script for pace-core Compliance
4
+ * pace-core Compliance Check Module
5
5
  * @package @jmruthers/pace-core
6
- * @module Scripts/check-pace-core-compliance
6
+ * @module Audit/Checks/Compliance
7
7
  *
8
- * Scans a consuming app's codebase to check compliance with pace-core usage.
9
- * Generates a report of violations and suggestions.
8
+ * Scans codebase for pace-core compliance violations including:
9
+ * - Restricted imports
10
+ * - Duplicate components/hooks/utils
11
+ * - Custom auth/RBAC code
12
+ * - Provider setup issues
13
+ * - Direct Supabase usage
14
+ * - Unnecessary wrappers
15
+ * - App discovery issues
10
16
  */
11
17
 
12
18
  const fs = require('fs');
13
19
  const path = require('path');
14
-
15
- // ANSI color codes for terminal output
16
- const colors = {
17
- reset: '\x1b[0m',
18
- red: '\x1b[31m',
19
- green: '\x1b[32m',
20
- yellow: '\x1b[33m',
21
- blue: '\x1b[34m',
22
- cyan: '\x1b[36m',
23
- bold: '\x1b[1m'
24
- };
20
+ const { getLineNumber, getRelativePath } = require('../utils.cjs');
25
21
 
26
22
  // Load manifest
27
23
  function loadManifest() {
28
- const manifestPath = path.join(__dirname, '../core-usage-manifest.json');
24
+ const manifestPath = path.join(__dirname, '../../../../core-usage-manifest.json');
29
25
  if (!fs.existsSync(manifestPath)) {
30
- console.error(`${colors.red}Error: core-usage-manifest.json not found at ${manifestPath}${colors.reset}`);
31
- process.exit(1);
26
+ throw new Error(`core-usage-manifest.json not found at ${manifestPath}`);
32
27
  }
33
28
  return JSON.parse(fs.readFileSync(manifestPath, 'utf8'));
34
29
  }
35
30
 
36
- // Find project root (look for package.json, going up from current dir or script location)
37
- function findProjectRoot(startDir = process.cwd()) {
38
- let current = path.resolve(startDir);
39
- while (current !== path.dirname(current)) {
40
- if (fs.existsSync(path.join(current, 'package.json'))) {
41
- return current;
42
- }
43
- current = path.dirname(current);
44
- }
45
- return startDir;
46
- }
47
-
48
- // Scan provider setup in main entry files
49
31
  function scanProviderSetup(filePath, content, relativePath) {
50
32
  const issues = [];
51
33
 
@@ -363,8 +345,114 @@ function scanRouterSetup(filePath, content, relativePath) {
363
345
  return issues;
364
346
  }
365
347
 
348
+ // Helper function to provide migration recommendations
349
+ function getMigrationRecommendation(method, operation) {
350
+ const recommendations = {
351
+ secureQuery: `Replace with: const supabase = useSecureSupabase(); const { data } = await supabase.from('table').select('*');`,
352
+ secureInsert: `Replace with: const supabase = useSecureSupabase(); const { data } = await supabase.from('table').insert(data).select().single();`,
353
+ secureUpdate: `Replace with: const supabase = useSecureSupabase(); const { data } = await supabase.from('table').update(data).eq('id', id).select().single();`,
354
+ secureDelete: `Replace with: const supabase = useSecureSupabase(); await supabase.from('table').delete().eq('id', id);`,
355
+ secureRpc: `Replace with: const supabase = useSecureSupabase(); const { data } = await supabase.rpc('function_name', params);`
356
+ };
357
+
358
+ return recommendations[method] || `Replace with useSecureSupabase() and use standard Supabase ${operation} API`;
359
+ }
360
+
361
+ // Scan for unnecessary wrappers around pace-core components and local components
362
+ function scanUnnecessaryWrappers(content, relativePath, manifest) {
363
+ const issues = [];
364
+
365
+ // Check if file imports from pace-core
366
+ const paceCoreImportPattern = /import\s+{([^}]+)}\s+from\s+['"]@jmruthers\/pace-core['"]/;
367
+ const paceCoreImportMatch = content.match(paceCoreImportPattern);
368
+
369
+ // Extract imported pace-core component names
370
+ let importedPaceCoreComponents = [];
371
+ if (paceCoreImportMatch) {
372
+ importedPaceCoreComponents = (paceCoreImportMatch[1] || '')
373
+ .split(',')
374
+ .map(name => name.trim().replace(/\s+as\s+\w+/, '')) // Remove aliases
375
+ .filter(name => manifest.components.includes(name));
376
+ }
377
+
378
+ // Find exported component definitions
379
+ const componentPattern = /export\s+(default\s+)?(function|const)\s+(\w+)\s*[=\(]/g;
380
+ const componentMatches = [...content.matchAll(componentPattern)];
381
+
382
+ componentMatches.forEach(match => {
383
+ const componentName = match[3];
384
+ const matchIndex = match.index;
385
+
386
+ // Skip if it's a test file or example file
387
+ if (relativePath.includes('.test.') || relativePath.includes('.spec.') ||
388
+ relativePath.includes('example') || relativePath.includes('Example')) {
389
+ return;
390
+ }
391
+
392
+ // Find the component body (simplified - just check if it's a simple wrapper)
393
+ const afterMatch = content.substring(matchIndex + match[0].length, Math.min(content.length, matchIndex + match[0].length + 500));
394
+
395
+ // Check if body has significant logic
396
+ const hasHooks = /use[A-Z]\w+/.test(afterMatch);
397
+ const hasState = /useState|useReducer|useRef/.test(afterMatch);
398
+ const hasConditionals = /if\s*\(|&&|\?|switch/.test(afterMatch);
399
+ const hasMultipleReturns = (afterMatch.match(/return/g) || []).length > 1;
400
+ const hasLoops = /for\s*\(|while\s*\(|\.map\s*\(/.test(afterMatch);
401
+
402
+ // Find JSX components used
403
+ const jsxComponentPattern = /<([A-Z][a-zA-Z0-9]*)[\s>\/]/g;
404
+ const jsxComponents = [];
405
+ let jsxMatch;
406
+ while ((jsxMatch = jsxComponentPattern.exec(afterMatch)) !== null) {
407
+ const jsxComponentName = jsxMatch[1];
408
+ if (jsxComponentName !== 'Fragment' &&
409
+ jsxComponentName !== componentName &&
410
+ !jsxComponents.includes(jsxComponentName)) {
411
+ jsxComponents.push(jsxComponentName);
412
+ }
413
+ }
414
+
415
+ // Check if it's a simple wrapper
416
+ if (jsxComponents.length === 1) {
417
+ const wrappedComponent = jsxComponents[0];
418
+ const wrappedComponentCount = (afterMatch.match(new RegExp(`<${wrappedComponent}`, 'gi')) || []).length;
419
+
420
+ const isSimpleWrapper =
421
+ wrappedComponentCount <= 2 &&
422
+ !hasState &&
423
+ !hasLoops &&
424
+ (!hasMultipleReturns || (hasMultipleReturns && !hasConditionals)) &&
425
+ (!hasHooks || /use(UnifiedAuth|Permissions|Can|RBAC)/.test(afterMatch));
426
+
427
+ if (isSimpleWrapper) {
428
+ const isPaceCoreComponent = importedPaceCoreComponents.includes(wrappedComponent);
429
+
430
+ let reason, recommendation;
431
+ if (isPaceCoreComponent) {
432
+ reason = `Component '${componentName}' appears to be an unnecessary wrapper around pace-core's '${wrappedComponent}'.`;
433
+ recommendation = `Use '${wrappedComponent}' directly from '@jmruthers/pace-core' instead.`;
434
+ } else {
435
+ reason = `Component '${componentName}' appears to be an unnecessary wrapper around '${wrappedComponent}'.`;
436
+ recommendation = `Remove the wrapper and use '${wrappedComponent}' directly.`;
437
+ }
438
+
439
+ issues.push({
440
+ component: componentName,
441
+ wrappedComponent: wrappedComponent,
442
+ file: relativePath,
443
+ line: getLineNumber(content, match[0]),
444
+ reason: reason,
445
+ recommendation: recommendation
446
+ });
447
+ }
448
+ }
449
+ });
450
+
451
+ return issues;
452
+ }
453
+
366
454
  // Scan file for violations
367
- function scanFile(filePath, manifest) {
455
+ function scanFile(filePath, manifest, projectRoot) {
368
456
  const violations = {
369
457
  restrictedImports: [],
370
458
  duplicateComponents: [],
@@ -375,6 +463,8 @@ function scanFile(filePath, manifest) {
375
463
  duplicateConfig: [],
376
464
  unprotectedPages: [],
377
465
  directSupabaseAuth: [],
466
+ directSupabaseClient: [], // Direct Supabase client usage instead of useSecureSupabase
467
+ deprecatedSecureDataAccess: [], // Deprecated useSecureDataAccess with secureQuery/secureInsert/etc
378
468
  providerSetupIssues: [],
379
469
  viteConfigIssues: [],
380
470
  routerSetupIssues: [],
@@ -383,7 +473,36 @@ function scanFile(filePath, manifest) {
383
473
  };
384
474
 
385
475
  const content = fs.readFileSync(filePath, 'utf8');
386
- const relativePath = path.relative(process.cwd(), filePath);
476
+ const relativePath = getRelativePath(filePath, projectRoot);
477
+
478
+ // Normalize path for cross-platform compatibility (handle both forward and backslash paths)
479
+ const normalizedPath = relativePath.replace(/\\/g, '/');
480
+
481
+ // Skip Edge Functions - they run in Deno, not React, so React hooks aren't available
482
+ // Direct Supabase auth calls are the correct approach in Edge Functions
483
+ const isEdgeFunction = normalizedPath.includes('supabase/functions/');
484
+
485
+ // Skip pace-core package files - compliance checks are for consuming applications, not the library itself
486
+ // The library must import these dependencies to build its components, and its own components/hooks/utils
487
+ // are the source of truth, not duplicates
488
+ const isPaceCorePackage = normalizedPath.includes('packages/core/') || normalizedPath.startsWith('packages/core/');
489
+ if (isPaceCorePackage) {
490
+ return violations; // Return empty violations for pace-core package files
491
+ }
492
+
493
+ // Skip scripts directory - these are utility/setup scripts, not application code
494
+ // Scripts may legitimately need direct database access for admin operations
495
+ const isScript = normalizedPath.startsWith('scripts/') || normalizedPath.includes('/scripts/');
496
+ if (isScript) {
497
+ return violations; // Return empty violations for script files
498
+ }
499
+
500
+ // Skip root-level src directory - in pace-core repository, this is a demo/showcase app, not a consuming app
501
+ // The audit is designed for consuming applications, not demo apps in the library repository
502
+ const isRootSrc = normalizedPath.startsWith('src/') && !normalizedPath.includes('packages/');
503
+ if (isRootSrc) {
504
+ return violations; // Return empty violations for root-level src files (demo apps)
505
+ }
387
506
 
388
507
  // Check for restricted imports
389
508
  manifest.restrictedImports.forEach(({ module, reason }) => {
@@ -478,6 +597,11 @@ function scanFile(filePath, manifest) {
478
597
  // RBAC/Auth Compliance Checks
479
598
  // ============================================
480
599
 
600
+ // Check if file imports from pace-core for auth/rbac (define at function scope)
601
+ const hasPaceCoreImport = /from\s+['"]@jmruthers\/pace-core/.test(content) ||
602
+ /from\s+['"]@jmruthers\/pace-core\/rbac/.test(content) ||
603
+ /from\s+['"]@jmruthers\/pace-core\/providers/.test(content);
604
+
481
605
  // Check for custom auth/rbac/permission code that doesn't import from pace-core
482
606
  const authRbacPatterns = [
483
607
  // Custom auth hooks
@@ -508,11 +632,6 @@ function scanFile(filePath, manifest) {
508
632
  { pattern: /export\s+(default\s+)?(function|const)\s+isPermitted\s*[=\(]/g, name: 'isPermitted', type: 'util' }
509
633
  ];
510
634
 
511
- // Check if file imports from pace-core for auth/rbac
512
- const hasPaceCoreImport = /from\s+['"]@jmruthers\/pace-core/.test(content) ||
513
- /from\s+['"]@jmruthers\/pace-core\/rbac/.test(content) ||
514
- /from\s+['"]@jmruthers\/pace-core\/providers/.test(content);
515
-
516
635
  authRbacPatterns.forEach(({ pattern, name, type, replacement }) => {
517
636
  // Create a new regex instance to avoid state issues
518
637
  const testPattern = new RegExp(pattern.source, pattern.flags);
@@ -530,7 +649,7 @@ function scanFile(filePath, manifest) {
530
649
  ].includes(name);
531
650
 
532
651
  // Check if the hook uses pace-core RBAC APIs (use a fresh regex to avoid state issues)
533
- const rbacApiPattern = /useRoleManagement|useSecureSupabase|useSecureDataAccess|useRBAC|usePermissions|useCan|rbac_role_grant|rbac_role_revoke|rbac_roles_list|data_rbac_apps_list/;
652
+ const rbacApiPattern = /useRoleManagement|useSecureSupabase|useRBAC|usePermissions|useCan|rbac_role_grant|rbac_role_revoke|rbac_roles_list|data_rbac_apps_list/;
534
653
  const usesPaceCoreRBAC = isCustomRBACHook && rbacApiPattern.test(content);
535
654
 
536
655
  // Check for @pace-core-compliant comment (use a fresh regex)
@@ -660,9 +779,22 @@ function scanFile(filePath, manifest) {
660
779
 
661
780
  // Skip Edge Functions - they run in Deno, not React, so React hooks are not available
662
781
  // Edge Functions MUST use direct supabase.auth.getUser() calls - this is correct and required
663
- // Handle both forward and backslash paths (Windows vs Unix)
664
- const normalizedPath = relativePath.replace(/\\/g, '/');
665
- const isEdgeFunction = normalizedPath.includes('supabase/functions/');
782
+ // isEdgeFunction is already defined above
783
+
784
+ // Other auth patterns - defined here so it's accessible in all scopes
785
+ const otherAuthPatterns = [
786
+ { pattern: /\.auth\.signIn\s*\(/g, method: 'signIn' },
787
+ { pattern: /\.auth\.signUp\s*\(/g, method: 'signUp' },
788
+ { pattern: /\.auth\.signOut\s*\(/g, method: 'signOut' },
789
+ { pattern: /\.auth\.onAuthStateChange\s*\(/g, method: 'onAuthStateChange' }
790
+ ];
791
+
792
+ // Check if file actually uses useUnifiedAuth hook (not just imports it)
793
+ // Define these at function scope so they're available throughout
794
+ const usesUnifiedAuthHook = /useUnifiedAuth\s*\(/.test(content);
795
+ const hasUnifiedAuthImport = /UnifiedAuthProvider/.test(content) ||
796
+ /useUnifiedAuth/.test(content) ||
797
+ /from\s+['"]@jmruthers\/pace-core\/providers/.test(content);
666
798
 
667
799
  // Skip all auth checks for Edge Functions - they cannot use React hooks
668
800
  if (isEdgeFunction) {
@@ -683,20 +815,6 @@ function scanFile(filePath, manifest) {
683
815
  { pattern: /\w+\.auth\.getUser\s*\(/g, method: 'getUser', specific: false },
684
816
  { pattern: /\w+\.auth\.getSession\s*\(/g, method: 'getSession', specific: false }
685
817
  ];
686
-
687
- // Other auth patterns
688
- const otherAuthPatterns = [
689
- { pattern: /\.auth\.signIn\s*\(/g, method: 'signIn' },
690
- { pattern: /\.auth\.signUp\s*\(/g, method: 'signUp' },
691
- { pattern: /\.auth\.signOut\s*\(/g, method: 'signOut' },
692
- { pattern: /\.auth\.onAuthStateChange\s*\(/g, method: 'onAuthStateChange' }
693
- ];
694
-
695
- // Check if file actually uses useUnifiedAuth hook (not just imports it)
696
- const usesUnifiedAuthHook = /useUnifiedAuth\s*\(/.test(content);
697
- const hasUnifiedAuthImport = /UnifiedAuthProvider/.test(content) ||
698
- /useUnifiedAuth/.test(content) ||
699
- /from\s+['"]@jmruthers\/pace-core\/providers/.test(content);
700
818
 
701
819
  // Check for usage of useCurrentUser hook (even if imported from local file)
702
820
  // This catches both local imports and direct usage
@@ -760,7 +878,8 @@ function scanFile(filePath, manifest) {
760
878
  const isInLineComment = /\/\/[^\n]*$/.test(lineUpToMatch);
761
879
 
762
880
  // Only skip if clearly in a comment - be conservative
763
- if (!isInLineComment) {
881
+ // Also skip Edge Functions - they run in Deno, not React, so React hooks aren't available
882
+ if (!isInLineComment && !isEdgeFunction) {
764
883
  violations.directSupabaseAuth.push({
765
884
  file: relativePath,
766
885
  line: getLineNumber(content, matchText),
@@ -794,7 +913,8 @@ function scanFile(filePath, manifest) {
794
913
  const lineUpToMatch = content.substring(lineStart, searchIndex);
795
914
  const isInLineComment = /\/\/[^\n]*$/.test(lineUpToMatch);
796
915
 
797
- if (!isInLineComment) {
916
+ // Skip Edge Functions - they run in Deno, not React, so React hooks aren't available
917
+ if (!isInLineComment && !isEdgeFunction) {
798
918
  // Calculate line number from index
799
919
  const lineNum = content.substring(0, searchIndex).split('\n').length;
800
920
 
@@ -855,7 +975,8 @@ function scanFile(filePath, manifest) {
855
975
  (doubleQuotes % 2 === 1 && beforeMatch.endsWith('"')) ||
856
976
  (backticks % 2 === 1 && beforeMatch.endsWith('`'));
857
977
 
858
- if (!isInLineComment && !isInBlockComment && !isInString && !usesUnifiedAuthHook) {
978
+ // Skip Edge Functions - they run in Deno, not React, so React hooks aren't available
979
+ if (!isInLineComment && !isInBlockComment && !isInString && !usesUnifiedAuthHook && !isEdgeFunction) {
859
980
  violations.directSupabaseAuth.push({
860
981
  file: relativePath,
861
982
  line: getLineNumber(content, matchText),
@@ -877,11 +998,11 @@ function scanFile(filePath, manifest) {
877
998
  { name: 'rbac_apps', type: 'config', recommendation: 'For admin operations, use useSecureSupabase. For application use, use pace-core RBAC APIs' },
878
999
  { name: 'rbac_app_pages', type: 'config', recommendation: 'For admin operations, use useSecureSupabase. For application use, use pace-core permission management APIs or PagePermissionGuard' },
879
1000
  { name: 'rbac_page_permissions', type: 'config', recommendation: 'For admin operations, use useSecureSupabase. For application use, use pace-core permission management APIs or PagePermissionGuard' },
880
- { name: 'rbac_user_units', type: 'user_data', recommendation: 'Use useSecureSupabase or useSecureDataAccess. For reading, consider data_user_unit_get RPC function' },
1001
+ { name: 'rbac_user_units', type: 'user_data', recommendation: 'Use useSecureSupabase. For reading, consider data_user_unit_get RPC function' },
881
1002
  // User data tables - acceptable to query but must use secure methods
882
- { name: 'rbac_user_profiles', type: 'user_data', recommendation: 'Use useSecureSupabase or useSecureDataAccess from pace-core to ensure organisation context is enforced' },
883
- { name: 'rbac_user_login_history', type: 'audit', recommendation: 'Use useSecureSupabase or useSecureDataAccess from pace-core. Login history is automatically tracked by UnifiedAuthProvider, but queries should use secure methods' },
884
- { name: 'rbac_user_sessions', type: 'session', recommendation: 'Use useSecureSupabase or useSecureDataAccess from pace-core to ensure organisation context is enforced' }
1003
+ { name: 'rbac_user_profiles', type: 'user_data', recommendation: 'Use useSecureSupabase from pace-core to ensure organisation context is enforced' },
1004
+ { name: 'rbac_user_login_history', type: 'audit', recommendation: 'Use useSecureSupabase from pace-core. Login history is automatically tracked by UnifiedAuthProvider, but queries should use secure methods' },
1005
+ { name: 'rbac_user_sessions', type: 'session', recommendation: 'Use useSecureSupabase from pace-core to ensure organisation context is enforced' }
885
1006
  ];
886
1007
 
887
1008
  // Detect admin/management context
@@ -906,26 +1027,19 @@ function scanFile(filePath, manifest) {
906
1027
  /useRBAC/.test(content) ||
907
1028
  /usePermissions/.test(content) ||
908
1029
  /useSecureSupabase/.test(content) ||
909
- /useSecureDataAccess/.test(content) ||
910
1030
  /PagePermissionGuard/.test(content);
911
1031
 
912
- // Check if file uses useSecureDataAccess hook
913
- const usesSecureDataAccess = /useSecureDataAccess/.test(content);
914
-
915
- // Check if file destructures secure methods from useSecureDataAccess
1032
+ // Check if file destructures secure methods (deprecated secureQuery/secureInsert/etc from useSecureDataAccess)
916
1033
  const hasSecureMethods = /(const|let)\s*\{[^}]*secure(Query|Update|Insert|Delete)/.test(content) ||
917
1034
  /secure(Query|Update|Insert|Delete)\s*\(/.test(content);
918
1035
 
919
1036
  // First, identify all variables assigned from secure hooks
920
- // Match patterns like: const supabase = useSecureSupabase(); or let client = useSecureDataAccess();
1037
+ // Match patterns like: const supabase = useSecureSupabase();
921
1038
  // Also detect fromSupabaseClient and wrapper patterns
922
1039
  const secureVariablePatterns = [
923
1040
  /const\s+(\w+)\s*=\s*useSecureSupabase\s*\(/g,
924
- /const\s+(\w+)\s*=\s*useSecureDataAccess\s*\(/g,
925
1041
  /let\s+(\w+)\s*=\s*useSecureSupabase\s*\(/g,
926
- /let\s+(\w+)\s*=\s*useSecureDataAccess\s*\(/g,
927
1042
  /(\w+)\s*=\s*useSecureSupabase\s*\(/g,
928
- /(\w+)\s*=\s*useSecureDataAccess\s*\(/g,
929
1043
  // Detect fromSupabaseClient usage
930
1044
  /const\s+(\w+)\s*=\s*fromSupabaseClient\s*\(/g,
931
1045
  /let\s+(\w+)\s*=\s*fromSupabaseClient\s*\(/g,
@@ -1051,12 +1165,55 @@ function scanFile(filePath, manifest) {
1051
1165
  const isConfigTable = type === 'config';
1052
1166
 
1053
1167
  // Check if the variable comes from a secure hook
1054
- // Check both the secureVariables set and also check if the variable is assigned from useSecureSupabase/useSecureDataAccess
1168
+ // Check both the secureVariables set and also check if the variable is assigned from useSecureSupabase
1055
1169
  // Escape special regex characters in variable name and use multiline flag to handle newlines
1056
1170
  const escapedVarName = variableName ? variableName.replace(/[.*+?^${}()|[\]\\]/g, '\\$&') : '';
1057
- const isUsingSecureVariable = secureVariables.has(variableName) ||
1058
- (variableName && new RegExp(`(const|let)\\s+${escapedVarName}\\s*=\\s*useSecureSupabase\\s*\\(`, 'm').test(content)) ||
1059
- (variableName && new RegExp(`(const|let)\\s+${escapedVarName}\\s*=\\s*useSecureDataAccess\\s*\\(`, 'm').test(content));
1171
+
1172
+ // Check if variable is declared locally (const/let = useSecureSupabase())
1173
+ const isDeclaredSecure = secureVariables.has(variableName) ||
1174
+ (variableName && new RegExp(`(const|let)\\s+${escapedVarName}\\s*=\\s*useSecureSupabase\\s*\\(`, 'm').test(content));
1175
+
1176
+ // Check if variable is passed as function parameter with secure type annotation
1177
+ // Look for function signatures with type annotations indicating secure client
1178
+ const isParameterSecure = variableName && (
1179
+ // Check in beforeMatch (200 chars before) for parameter type annotations
1180
+ new RegExp(`\\b${escapedVarName}\\s*:\\s*.*(SecureSupabaseClient|useSecureSupabase|ReturnType.*useSecureSupabase)`, 'm').test(beforeMatch) ||
1181
+ // Check full content for function signatures with secure type annotations
1182
+ new RegExp(`(function|=>|async\\s+function|const\\s+\\w+\\s*=\\s*(async\\s+)?(function|=>))[^(]*\\([^)]*\\b${escapedVarName}\\s*:\\s*.*(SecureSupabaseClient|useSecureSupabase|ReturnType.*useSecureSupabase)`, 'm').test(content) ||
1183
+ // Check for ReturnType<typeof import pattern (common in TypeScript)
1184
+ new RegExp(`\\b${escapedVarName}\\s*:\\s*.*ReturnType.*useSecureSupabase`, 'm').test(content) ||
1185
+ // Check for NonNullable<ReturnType<typeof useSecureSupabase>> pattern
1186
+ new RegExp(`\\b${escapedVarName}\\s*:\\s*.*NonNullable.*ReturnType.*useSecureSupabase`, 'm').test(content)
1187
+ );
1188
+
1189
+ // Check for common secure variable names (heuristic for secure usage)
1190
+ const isSecureVariableName = variableName && /^(secureSupabase|secureClient|secureDb|secure)$/i.test(variableName);
1191
+
1192
+ // Check for comments indicating secure usage (pace-core-compliant, useSecureSupabase, etc.)
1193
+ // Also check for @pace-core-compliant annotation which can suppress false positives
1194
+ const hasSecureComment = variableName && (
1195
+ // Check in beforeMatch for comments
1196
+ new RegExp(`(pace-core-compliant|useSecureSupabase|secureSupabase|@pace-core-compliant)`, 'i').test(beforeMatch) ||
1197
+ // Check for comments on previous lines (up to 10 lines back for better coverage)
1198
+ (() => {
1199
+ const lines = content.substring(0, matchIndex).split('\n');
1200
+ const recentLines = lines.slice(Math.max(0, lines.length - 10)).join('\n');
1201
+ return new RegExp(`(pace-core-compliant|useSecureSupabase|secureSupabase|@pace-core-compliant)`, 'i').test(recentLines);
1202
+ })()
1203
+ );
1204
+
1205
+ // Check for @pace-core-compliant annotation on the same line or previous lines (suppression mechanism)
1206
+ const hasComplianceAnnotation = (() => {
1207
+ const lines = content.substring(0, matchIndex).split('\n');
1208
+ const currentLineIdx = lines.length - 1;
1209
+ // Check current line and up to 3 previous lines for @pace-core-compliant
1210
+ const checkLines = lines.slice(Math.max(0, currentLineIdx - 3), currentLineIdx + 1);
1211
+ return new RegExp(`@pace-core-compliant`, 'i').test(checkLines.join('\n'));
1212
+ })();
1213
+
1214
+ // Combine all checks
1215
+ // If @pace-core-compliant annotation is present, trust it (suppression mechanism)
1216
+ const isUsingSecureVariable = hasComplianceAnnotation || isDeclaredSecure || isParameterSecure || (isSecureVariableName && hasSecureComment);
1060
1217
 
1061
1218
  // Determine severity based on context
1062
1219
  let severity = 'error';
@@ -1068,7 +1225,7 @@ function scanFile(filePath, manifest) {
1068
1225
  continue;
1069
1226
  } else {
1070
1227
  severity = 'error';
1071
- reason = `Direct query to RBAC table '${tableName}' detected. This table can be queried, but you MUST use useSecureSupabase or useSecureDataAccess to ensure organisation context is enforced and RLS policies are respected.`;
1228
+ reason = `Direct query to RBAC table '${tableName}' detected. This table can be queried, but you MUST use useSecureSupabase to ensure organisation context is enforced and RLS policies are respected.`;
1072
1229
  }
1073
1230
  } else if (isConfigTable) {
1074
1231
  if (isUsingSecureVariable) {
@@ -1078,7 +1235,7 @@ function scanFile(filePath, manifest) {
1078
1235
  } else if (isAdminContext) {
1079
1236
  // Admin operations without secure methods - warning
1080
1237
  severity = 'warning';
1081
- reason = `Admin operation on configuration table '${tableName}' detected. Ensure you're using useSecureSupabase or useSecureDataAccess for security.`;
1238
+ reason = `Admin operation on configuration table '${tableName}' detected. Ensure you're using useSecureSupabase for security.`;
1082
1239
  } else {
1083
1240
  // Not admin context and not using secure methods - error
1084
1241
  severity = 'error';
@@ -1140,8 +1297,8 @@ function scanFile(filePath, manifest) {
1140
1297
  } else if (isConfigTable) {
1141
1298
  // Config tables using secure methods - acceptable for admin operations
1142
1299
  // If using secureQuery/secureUpdate/etc., it's already using secure methods
1143
- if (isAdminContext || usesSecureDataAccess) {
1144
- // Config tables in admin context or using secure methods - acceptable
1300
+ if (isAdminContext) {
1301
+ // Config tables in admin context - acceptable
1145
1302
  continue; // Skip - this is correct usage for admin operations
1146
1303
  } else {
1147
1304
  severity = 'error';
@@ -1217,10 +1374,9 @@ function scanFile(filePath, manifest) {
1217
1374
  // Check if using secure variable (check both set and direct pattern match)
1218
1375
  // Escape special regex characters in variable name and use multiline flag to handle newlines
1219
1376
  const escapedVarName = variableName ? variableName.replace(/[.*+?^${}()|[\]\\]/g, '\\$&') : '';
1220
- // Check if variable is declared with useSecureSupabase or useSecureDataAccess
1377
+ // Check if variable is declared with useSecureSupabase
1221
1378
  const isDeclaredSecure = (variableName && secureVariables.has(variableName)) ||
1222
- (variableName && new RegExp(`(const|let)\\s+${escapedVarName}\\s*=\\s*useSecureSupabase\\s*\\(`, 'm').test(content)) ||
1223
- (variableName && new RegExp(`(const|let)\\s+${escapedVarName}\\s*=\\s*useSecureDataAccess\\s*\\(`, 'm').test(content));
1379
+ (variableName && new RegExp(`(const|let)\\s+${escapedVarName}\\s*=\\s*useSecureSupabase\\s*\\(`, 'm').test(content));
1224
1380
  // Check if variable is passed as parameter with useSecureSupabase type annotation
1225
1381
  // Look for the parameter in function signatures (check both beforeMatch and full content)
1226
1382
  const isParameterSecure = variableName && (
@@ -1262,12 +1418,12 @@ function scanFile(filePath, manifest) {
1262
1418
  let severity = 'error';
1263
1419
  let reason = '';
1264
1420
 
1265
- if (isConfigTable && (isAdminContext || usesSecureDataAccess) && (isUsingSecureVariable || hasSecureMethods)) {
1421
+ if (isConfigTable && isAdminContext && (isUsingSecureVariable || hasSecureMethods)) {
1266
1422
  // Admin operations with secure methods - acceptable, skip
1267
1423
  continue;
1268
1424
  } else if (isConfigTable && isAdminContext && !isUsingSecureVariable && !hasSecureMethods) {
1269
1425
  severity = 'warning';
1270
- reason = `Admin operation on configuration table '${tableName}' detected. Ensure you're using useSecureSupabase or useSecureDataAccess for security.`;
1426
+ reason = `Admin operation on configuration table '${tableName}' detected. Ensure you're using useSecureSupabase for security.`;
1271
1427
  } else if (isConfigTable && !isAdminContext) {
1272
1428
  severity = 'error';
1273
1429
  reason = `Direct query to RBAC configuration table '${tableName}' detected. These are system configuration tables. For admin operations, use useSecureSupabase. For application use, use pace-core RBAC APIs.`;
@@ -1277,7 +1433,7 @@ function scanFile(filePath, manifest) {
1277
1433
  continue;
1278
1434
  }
1279
1435
  severity = 'error';
1280
- reason = `Direct query to RBAC table '${tableName}' detected. This table can be queried, but you MUST use useSecureSupabase or useSecureDataAccess to ensure organisation context is enforced and RLS policies are respected.`;
1436
+ reason = `Direct query to RBAC table '${tableName}' detected. This table can be queried, but you MUST use useSecureSupabase to ensure organisation context is enforced and RLS policies are respected.`;
1281
1437
  } else {
1282
1438
  severity = 'error';
1283
1439
  reason = `Direct query to RBAC table '${tableName}' detected. Use pace-core RBAC hooks, RPC functions, or useSecureSupabase instead.`;
@@ -1462,6 +1618,61 @@ function scanFile(filePath, manifest) {
1462
1618
  // For .from() patterns, match[1] is the CRUD method; for secure* patterns, operation is already set
1463
1619
  const crudMethod = method === 'from' ? match[1] : operation;
1464
1620
 
1621
+ // Extract variable name for .from() patterns (for secure* patterns, we skip variable detection)
1622
+ let variableName = null;
1623
+ if (method === 'from') {
1624
+ // Look backwards to find the variable name before .from()
1625
+ const beforeMatch = content.substring(Math.max(0, matchIndex - 200), matchIndex);
1626
+ const parts = beforeMatch.split('.from');
1627
+ if (parts.length > 0) {
1628
+ const beforeFrom = parts[parts.length - 1].trim();
1629
+ const words = beforeFrom.match(/\b\w+\b/g);
1630
+ if (words && words.length > 0) {
1631
+ variableName = words[words.length - 1];
1632
+ }
1633
+ }
1634
+ }
1635
+
1636
+ // Check if using secure variable (only for .from() patterns)
1637
+ let isUsingSecureVariable = false;
1638
+ if (method === 'from' && variableName) {
1639
+ const escapedVarName = variableName.replace(/[.*+?^${}()|[\]\\]/g, '\\$&');
1640
+
1641
+ // Check if variable is declared locally
1642
+ const isDeclaredSecure = secureVariables.has(variableName) ||
1643
+ new RegExp(`(const|let)\\s+${escapedVarName}\\s*=\\s*useSecureSupabase\\s*\\(`, 'm').test(content);
1644
+
1645
+ // Check if variable is passed as function parameter with secure type annotation
1646
+ const beforeMatch = content.substring(Math.max(0, matchIndex - 200), matchIndex);
1647
+ const isParameterSecure =
1648
+ new RegExp(`\\b${escapedVarName}\\s*:\\s*.*(SecureSupabaseClient|useSecureSupabase|ReturnType.*useSecureSupabase)`, 'm').test(beforeMatch) ||
1649
+ new RegExp(`(function|=>|async\\s+function|const\\s+\\w+\\s*=\\s*(async\\s+)?(function|=>))[^(]*\\([^)]*\\b${escapedVarName}\\s*:\\s*.*(SecureSupabaseClient|useSecureSupabase|ReturnType.*useSecureSupabase)`, 'm').test(content) ||
1650
+ new RegExp(`\\b${escapedVarName}\\s*:\\s*.*ReturnType.*useSecureSupabase`, 'm').test(content) ||
1651
+ new RegExp(`\\b${escapedVarName}\\s*:\\s*.*NonNullable.*ReturnType.*useSecureSupabase`, 'm').test(content);
1652
+
1653
+ // Check for common secure variable names
1654
+ const isSecureVariableName = /^(secureSupabase|secureClient|secureDb|secure)$/i.test(variableName);
1655
+
1656
+ // Check for comments indicating secure usage
1657
+ const hasSecureComment =
1658
+ new RegExp(`(pace-core-compliant|useSecureSupabase|secureSupabase|@pace-core-compliant)`, 'i').test(beforeMatch) ||
1659
+ (() => {
1660
+ const lines = content.substring(0, matchIndex).split('\n');
1661
+ const recentLines = lines.slice(Math.max(0, lines.length - 10)).join('\n');
1662
+ return new RegExp(`(pace-core-compliant|useSecureSupabase|secureSupabase|@pace-core-compliant)`, 'i').test(recentLines);
1663
+ })();
1664
+
1665
+ // Check for @pace-core-compliant annotation (suppression mechanism)
1666
+ const hasComplianceAnnotation = (() => {
1667
+ const lines = content.substring(0, matchIndex).split('\n');
1668
+ const currentLineIdx = lines.length - 1;
1669
+ const checkLines = lines.slice(Math.max(0, currentLineIdx - 3), currentLineIdx + 1);
1670
+ return new RegExp(`@pace-core-compliant`, 'i').test(checkLines.join('\n'));
1671
+ })();
1672
+
1673
+ isUsingSecureVariable = hasComplianceAnnotation || isDeclaredSecure || isParameterSecure || (isSecureVariableName && hasSecureComment);
1674
+ }
1675
+
1465
1676
  // Skip if in a line comment
1466
1677
  const lineStart = content.lastIndexOf('\n', matchIndex) + 1;
1467
1678
  const lineUpToMatch = content.substring(lineStart, matchIndex);
@@ -1473,6 +1684,16 @@ function scanFile(filePath, manifest) {
1473
1684
  let severity = 'error';
1474
1685
  let example = '';
1475
1686
 
1687
+ // If using secure variable for user_data tables, skip (correct usage)
1688
+ if (isUserData && isUsingSecureVariable) {
1689
+ continue;
1690
+ }
1691
+
1692
+ // If using secure variable for config tables, skip (correct usage)
1693
+ if (isConfig && isUsingSecureVariable) {
1694
+ continue;
1695
+ }
1696
+
1476
1697
  if (isRole) {
1477
1698
  // Role mutations should use RPC functions
1478
1699
  const isGrant = crudMethod === 'insert' || crudMethod === 'upsert';
@@ -1495,24 +1716,25 @@ function scanFile(filePath, manifest) {
1495
1716
  if (method && method.startsWith('secure')) {
1496
1717
  // Using secureInsert/secureUpdate/secureDelete - this is correct, don't flag
1497
1718
  continue;
1498
- } else if (isAdminContext && usesSecureDataAccess) {
1499
- // Admin context and using useSecureDataAccess - check if using secure methods
1719
+ } else if (isAdminContext) {
1720
+ // Admin context - check if using secure methods
1500
1721
  // If the operation uses secureQuery/secureUpdate/etc, it's already handled above
1501
1722
  // This case is for direct .from() calls in admin context
1502
1723
  severity = 'warning';
1503
- reason = `Admin operation (${crudMethod}) on configuration table '${table}' detected. Ensure you're using useSecureSupabase or useSecureDataAccess for security.`;
1504
- replacement = 'Use useSecureSupabase or useSecureDataAccess from pace-core for admin operations on configuration tables';
1505
- } else if (isAdminContext) {
1506
- severity = 'warning';
1507
- reason = `Admin operation (${crudMethod}) on configuration table '${table}' detected. Ensure you're using useSecureSupabase or useSecureDataAccess for security.`;
1508
- replacement = 'Use useSecureSupabase or useSecureDataAccess from pace-core for admin operations on configuration tables';
1724
+ reason = `Admin operation (${crudMethod}) on configuration table '${table}' detected. Ensure you're using useSecureSupabase for security.`;
1725
+ replacement = 'Use useSecureSupabase from pace-core for admin operations on configuration tables';
1509
1726
  } else {
1510
1727
  reason = `Direct ${crudMethod} operation on configuration table '${table}' detected. These are system configuration tables. For admin operations, use useSecureSupabase.`;
1511
- replacement = 'Use useSecureSupabase or useSecureDataAccess from pace-core for admin operations';
1728
+ replacement = 'Use useSecureSupabase from pace-core for admin operations';
1512
1729
  }
1513
1730
  } else if (isUserData) {
1514
- reason = `Direct ${crudMethod} operation on '${table}' detected. Use useSecureSupabase or useSecureDataAccess to ensure organisation context is enforced.`;
1515
- replacement = 'Use useSecureSupabase or useSecureDataAccess from pace-core';
1731
+ // User data tables - should use secure methods
1732
+ if (isUsingSecureVariable) {
1733
+ // Already handled above - skip
1734
+ continue;
1735
+ }
1736
+ reason = `Direct ${crudMethod} operation on '${table}' detected. Use useSecureSupabase to ensure organisation context is enforced.`;
1737
+ replacement = 'Use useSecureSupabase from pace-core';
1516
1738
  } else {
1517
1739
  reason = `Direct ${crudMethod} operation on '${table}' detected. Use pace-core permission management APIs or documented RPC functions instead.`;
1518
1740
  replacement = 'pace-core permission management APIs or RPC functions';
@@ -1621,7 +1843,7 @@ function scanFile(filePath, manifest) {
1621
1843
  type,
1622
1844
  file: relativePath,
1623
1845
  line: getLineNumber(content, hookStartMatch[0]),
1624
- reason: `Custom ${type} '${name}' detected that implements role/permission management logic. Even though it may use some pace-core utilities (like useSecureDataAccess), it should use pace-core RBAC APIs instead of implementing custom logic.`,
1846
+ reason: `Custom ${type} '${name}' detected that implements role/permission management logic. Even though it may use some pace-core utilities, it should use pace-core RBAC APIs instead of implementing custom logic.`,
1625
1847
  replacement,
1626
1848
  severity: 'error'
1627
1849
  });
@@ -1632,7 +1854,7 @@ function scanFile(filePath, manifest) {
1632
1854
  type,
1633
1855
  file: relativePath,
1634
1856
  line: getLineNumber(content, hookStartMatch[0]),
1635
- reason: `Custom ${type} '${name}' detected that implements role/permission management logic. Even though it may use some pace-core utilities (like useSecureDataAccess), it should use pace-core RBAC APIs instead of implementing custom logic.`,
1857
+ reason: `Custom ${type} '${name}' detected that implements role/permission management logic. Even though it may use some pace-core utilities, it should use pace-core RBAC APIs instead of implementing custom logic.`,
1636
1858
  replacement,
1637
1859
  severity: 'error'
1638
1860
  });
@@ -1643,7 +1865,7 @@ function scanFile(filePath, manifest) {
1643
1865
  type,
1644
1866
  file: relativePath,
1645
1867
  line: getLineNumber(content, hookStartMatch[0]),
1646
- reason: `Custom ${type} '${name}' detected that implements role/permission management logic. Even though it may use some pace-core utilities (like useSecureDataAccess), it should use pace-core RBAC APIs instead of implementing custom logic.`,
1868
+ reason: `Custom ${type} '${name}' detected that implements role/permission management logic. Even though it may use some pace-core utilities, it should use pace-core RBAC APIs instead of implementing custom logic.`,
1647
1869
  replacement,
1648
1870
  severity: 'error'
1649
1871
  });
@@ -1700,7 +1922,7 @@ function scanFile(filePath, manifest) {
1700
1922
  const operatesOnPagePermissions = /rbac_page_permissions/.test(content);
1701
1923
  const operatesOnRoleTables = /rbac_(organisation|event_app|global)_roles/.test(content);
1702
1924
  const operatesOnConfigTables = /rbac_apps|rbac_app_pages/.test(content);
1703
- const usesSecureMethods = /useSecureDataAccess|useSecureSupabase|secureQuery|secureUpdate|secureInsert|secureDelete/.test(content);
1925
+ const usesSecureMethods = /useSecureSupabase|secureQuery|secureUpdate|secureInsert|secureDelete/.test(content);
1704
1926
 
1705
1927
  // Only flag as permission management if:
1706
1928
  // 1. It's explicitly a permission management component AND operates on permission/role tables
@@ -1729,8 +1951,8 @@ function scanFile(filePath, manifest) {
1729
1951
  type: 'configuration management component',
1730
1952
  file: relativePath,
1731
1953
  line: getLineNumber(content, content.match(componentPattern)[0]),
1732
- reason: `Configuration management component '${name}' detected. Ensure you're using useSecureDataAccess or useSecureSupabase for secure operations on configuration tables.`,
1733
- replacement: 'Use useSecureDataAccess or useSecureSupabase from pace-core for admin operations on configuration tables',
1954
+ reason: `Configuration management component '${name}' detected. Ensure you're using useSecureSupabase for secure operations on configuration tables.`,
1955
+ replacement: 'Use useSecureSupabase from pace-core for admin operations on configuration tables',
1734
1956
  severity: 'warning'
1735
1957
  });
1736
1958
  }
@@ -1761,7 +1983,10 @@ function scanFile(filePath, manifest) {
1761
1983
  }
1762
1984
 
1763
1985
  // Check Vite configuration
1764
- if (relativePath.match(/vite\.config\.(ts|js|tsx|jsx)$/)) {
1986
+ // Skip root-level vite.config files - these are typically for library/monorepo development, not consuming apps
1987
+ // The audit recommendations (exclude @jmruthers/pace-core, dedupe) are for consuming apps, not library dev setups
1988
+ const isRootViteConfig = /^vite\.config\.(ts|js|tsx|jsx)$/.test(relativePath);
1989
+ if (!isRootViteConfig && relativePath.match(/vite\.config\.(ts|js|tsx|jsx)$/)) {
1765
1990
  const viteIssues = scanViteConfig(filePath, content, relativePath);
1766
1991
  violations.viteConfigIssues.push(...viteIssues);
1767
1992
  }
@@ -1956,633 +2181,454 @@ function scanFile(filePath, manifest) {
1956
2181
  }
1957
2182
  }
1958
2183
 
1959
- return violations;
1960
- }
1961
-
1962
- // Scan for unnecessary wrappers around pace-core components and local components
1963
- function scanUnnecessaryWrappers(content, relativePath, manifest) {
1964
- const issues = [];
1965
-
1966
- // Check if file imports from pace-core
1967
- const paceCoreImportPattern = /import\s+{([^}]+)}\s+from\s+['"]@jmruthers\/pace-core['"]/;
1968
- const paceCoreImportMatch = content.match(paceCoreImportPattern);
1969
-
1970
- // Extract imported pace-core component names
1971
- let importedPaceCoreComponents = [];
1972
- if (paceCoreImportMatch) {
1973
- importedPaceCoreComponents = (paceCoreImportMatch[1] || '')
1974
- .split(',')
1975
- .map(name => name.trim().replace(/\s+as\s+\w+/, '')) // Remove aliases
1976
- .filter(name => manifest.components.includes(name));
1977
- }
1978
-
1979
- // Also find all imported components (local and pace-core) to check for wrappers
1980
- const allImportPattern = /import\s+(?:(?:{([^}]+)})|(\w+))\s+from\s+['"]([^'"]+)['"]/g;
1981
- const allImports = [];
1982
- let importMatch;
1983
- while ((importMatch = allImportPattern.exec(content)) !== null) {
1984
- const namedImports = importMatch[1]; // { Component1, Component2 }
1985
- const defaultImport = importMatch[2]; // Component
1986
- const modulePath = importMatch[3];
1987
-
1988
- if (namedImports) {
1989
- namedImports.split(',').forEach(name => {
1990
- const cleanName = name.trim().replace(/\s+as\s+(\w+)/, ''); // Remove aliases but keep original
1991
- allImports.push({ name: cleanName, module: modulePath });
1992
- });
1993
- }
1994
- if (defaultImport) {
1995
- allImports.push({ name: defaultImport, module: modulePath });
1996
- }
1997
- }
1998
-
1999
- // Find exported component definitions
2000
- // Match: export (default)? (function|const) ComponentName = ...
2001
- const componentPattern = /export\s+(default\s+)?(function|const)\s+(\w+)\s*[=\(]/g;
2002
- const componentMatches = [...content.matchAll(componentPattern)];
2184
+ // ============================================
2185
+ // Check for Direct Supabase Client Usage
2186
+ // ============================================
2187
+ // Detect when consuming apps use createClient from @supabase/supabase-js
2188
+ // and then use that client for database queries instead of useSecureSupabase
2189
+ // This is a critical security issue as it bypasses RLS and organisation context
2003
2190
 
2004
- componentMatches.forEach(match => {
2005
- const componentName = match[3];
2006
- const matchIndex = match.index;
2191
+ // Skip Edge Functions - they run in Deno and must use createClient
2192
+ // Reuse isEdgeFunction declared at the top of the function
2193
+ if (!isEdgeFunction) {
2194
+ // Check for createClient import from @supabase/supabase-js
2195
+ const createClientImportPattern = /import\s+{\s*createClient\s*}\s+from\s+['"]@supabase\/supabase-js['"]/;
2196
+ const hasCreateClientImport = createClientImportPattern.test(content);
2007
2197
 
2008
- // Skip if it's a test file or example file
2009
- if (relativePath.includes('.test.') || relativePath.includes('.spec.') ||
2010
- relativePath.includes('example') || relativePath.includes('Example')) {
2011
- return;
2012
- }
2198
+ // Check for createClient usage
2199
+ const createClientUsagePattern = /createClient\s*\(/g;
2200
+ const createClientMatches = content.match(createClientUsagePattern);
2201
+ const hasCreateClientUsage = createClientMatches && createClientMatches.length > 0;
2013
2202
 
2014
- // Find the component body
2015
- // Look for the opening brace after the function/const declaration
2016
- let braceCount = 0;
2017
- let bodyStart = -1;
2018
- let bodyEnd = -1;
2019
- let inBody = false;
2203
+ // Check if file uses useSecureSupabase (correct usage)
2204
+ const usesSecureSupabase = /useSecureSupabase/.test(content) ||
2205
+ /from\s+['"]@jmruthers\/pace-core\/rbac['"]/.test(content);
2020
2206
 
2021
- for (let i = matchIndex + match[0].length; i < content.length; i++) {
2022
- const char = content[i];
2023
- if (char === '{' && !inBody) {
2024
- bodyStart = i;
2025
- inBody = true;
2026
- braceCount = 1;
2027
- } else if (char === '{') {
2028
- braceCount++;
2029
- } else if (char === '}') {
2030
- braceCount--;
2031
- if (braceCount === 0 && inBody) {
2032
- bodyEnd = i;
2033
- break;
2034
- }
2207
+ // Find all variables assigned from createClient
2208
+ const createClientVariablePattern = /(const|let|var)\s+(\w+)\s*=\s*createClient\s*\(/g;
2209
+ const nonSecureClients = new Set();
2210
+ let match;
2211
+ while ((match = createClientVariablePattern.exec(content)) !== null) {
2212
+ if (match[2]) {
2213
+ nonSecureClients.add(match[2]);
2035
2214
  }
2036
2215
  }
2037
2216
 
2038
- if (bodyStart === -1 || bodyEnd === -1) {
2039
- return; // Couldn't find component body
2040
- }
2041
-
2042
- const componentBody = content.substring(bodyStart + 1, bodyEnd).trim();
2043
-
2044
- // Check if body has significant logic (hooks, state, conditionals, etc.)
2045
- const hasHooks = /use[A-Z]\w+/.test(componentBody);
2046
- const hasState = /useState|useReducer|useRef/.test(componentBody);
2047
- const hasConditionals = /if\s*\(|&&|\?|switch/.test(componentBody);
2048
- const hasMultipleReturns = (componentBody.match(/return/g) || []).length > 1;
2049
- const hasLoops = /for\s*\(|while\s*\(|\.map\s*\(/.test(componentBody);
2050
-
2051
- // Find all JSX components used in the body
2052
- // Match JSX opening tags: <ComponentName or <ComponentName>
2053
- const jsxComponentPattern = /<([A-Z][a-zA-Z0-9]*)[\s>\/]/g;
2054
- const jsxComponents = [];
2055
- let jsxMatch;
2056
- while ((jsxMatch = jsxComponentPattern.exec(componentBody)) !== null) {
2057
- const jsxComponentName = jsxMatch[1];
2058
- // Skip React fragments and the component itself
2059
- if (jsxComponentName !== 'Fragment' &&
2060
- jsxComponentName !== componentName &&
2061
- !jsxComponents.includes(jsxComponentName)) {
2062
- jsxComponents.push(jsxComponentName);
2217
+ // Check for database queries (.from() calls) using non-secure clients
2218
+ // Pattern: variable.from('table_name')
2219
+ const fromPattern = /\.from\s*\(\s*['"]([^'"]+)['"]\s*\)/g;
2220
+ let fromMatch;
2221
+ while ((fromMatch = fromPattern.exec(content)) !== null) {
2222
+ const matchIndex = fromMatch.index;
2223
+ const tableName = fromMatch[1];
2224
+
2225
+ // Skip RBAC tables (already checked above)
2226
+ if (tableName.startsWith('rbac_')) {
2227
+ continue;
2063
2228
  }
2064
- }
2065
-
2066
- // Check if component is a simple wrapper around another component
2067
- // Pattern 1: Just returns a single component (pace-core or local)
2068
- // Pattern 2: Only does auth/permission checks and returns a component
2069
- // Pattern 3: Minimal logic that just forwards to another component
2070
-
2071
- // Check for auth/permission check patterns (common in page wrappers)
2072
- const hasAuthCheck = /useUnifiedAuth|usePermissions|useCan|PagePermissionGuard|PermissionGuard|useRBAC/.test(componentBody);
2073
- const hasEarlyReturn = /if\s*\([^)]*\)\s*return/.test(componentBody);
2074
-
2075
- // Count conditional returns (early returns for auth checks)
2076
- const conditionalReturns = (componentBody.match(/if\s*\([^)]*\)\s*return/g) || []).length;
2077
-
2078
- // If there's only one JSX component used and minimal logic, it's likely a wrapper
2079
- if (jsxComponents.length === 1) {
2080
- const wrappedComponent = jsxComponents[0];
2081
- const wrappedComponentCount = (componentBody.match(new RegExp(`<${wrappedComponent}`, 'gi')) || []).length;
2082
-
2083
- // Check if it's a simple wrapper:
2084
- // 1. Uses only one other component
2085
- // 2. No state management
2086
- // 3. No loops
2087
- // 4. Component appears only once or twice (opening and closing tag)
2088
- // 5. Either no logic at all, OR only auth/permission checks with early returns
2089
-
2090
- // Allow auth/permission checks but still flag as wrapper if that's all it does
2091
- // Pattern: uses auth hook, has early return(s) for auth checks, then returns the wrapped component
2092
- const hasOnlyAuthLogic = hasAuthCheck &&
2093
- !hasState &&
2094
- !hasLoops &&
2095
- (conditionalReturns === 0 || (conditionalReturns <= 2 && hasEarlyReturn)) &&
2096
- (!hasConditionals || conditionalReturns <= 2);
2097
-
2098
- // Check if hooks are only auth-related
2099
- const authHookPattern = /use(UnifiedAuth|Permissions|Can|RBAC)/;
2100
- const allHooks = componentBody.match(/use[A-Z]\w+/g) || [];
2101
- const onlyAuthHooks = allHooks.length === 0 || allHooks.every(hook => authHookPattern.test(hook));
2102
2229
 
2103
- const isSimpleWrapper =
2104
- wrappedComponentCount <= 2 && // Opening and closing tag, or self-closing
2105
- !hasState &&
2106
- !hasLoops &&
2107
- (hasMultipleReturns === false || (hasMultipleReturns && hasOnlyAuthLogic && conditionalReturns <= 2)) &&
2108
- (!hasConditionals || hasOnlyAuthLogic) &&
2109
- (!hasHooks || (onlyAuthHooks && hasOnlyAuthLogic));
2230
+ // Skip if in a line comment
2231
+ const lineStart = content.lastIndexOf('\n', matchIndex) + 1;
2232
+ const lineUpToMatch = content.substring(lineStart, matchIndex);
2233
+ const isInLineComment = /\/\/[^\n]*$/.test(lineUpToMatch);
2110
2234
 
2111
- if (isSimpleWrapper) {
2112
- // Check if the wrapped component is from pace-core or local
2113
- const wrappedComponentImport = allImports.find(imp => imp.name === wrappedComponent);
2114
- const isPaceCoreComponent = wrappedComponentImport &&
2115
- wrappedComponentImport.module === '@jmruthers/pace-core';
2235
+ if (isInLineComment) {
2236
+ continue;
2237
+ }
2238
+
2239
+ // Find the variable name before .from()
2240
+ const beforeMatch = content.substring(Math.max(0, matchIndex - 200), matchIndex);
2241
+ const parts = beforeMatch.split('.from');
2242
+ let variableName = null;
2243
+ if (parts.length > 0) {
2244
+ const beforeFrom = parts[parts.length - 1].trim();
2245
+ const words = beforeFrom.match(/\b\w+\b/g);
2246
+ if (words && words.length > 0) {
2247
+ variableName = words[words.length - 1];
2248
+ }
2249
+ }
2250
+
2251
+ // Check if this variable is from createClient (non-secure)
2252
+ if (variableName && nonSecureClients.has(variableName)) {
2253
+ // Check if it's in a config file (acceptable for centralized config)
2254
+ const isConfigFile = /config|supabase|client/i.test(relativePath) &&
2255
+ (relativePath.includes('supabase.ts') ||
2256
+ relativePath.includes('supabase.js') ||
2257
+ relativePath.includes('client.ts') ||
2258
+ relativePath.includes('client.js'));
2116
2259
 
2117
- let reason, recommendation;
2118
- if (isPaceCoreComponent) {
2119
- reason = `Component '${componentName}' appears to be an unnecessary wrapper around pace-core's '${wrappedComponent}'. It only forwards props without adding functionality.`;
2120
- recommendation = `Use '${wrappedComponent}' directly from '@jmruthers/pace-core' instead of wrapping it in '${componentName}'.`;
2121
- } else if (hasOnlyAuthLogic) {
2122
- reason = `Component '${componentName}' is an unnecessary wrapper around '${wrappedComponent}'. It only performs auth/permission checks and returns the wrapped component.`;
2123
- recommendation = `Merge the auth/permission logic into '${wrappedComponent}' and rename it to '${componentName}', or use PagePermissionGuard from pace-core to protect the route instead.`;
2124
- } else {
2125
- reason = `Component '${componentName}' appears to be an unnecessary wrapper around '${wrappedComponent}'. It only forwards props without adding functionality.`;
2126
- recommendation = `Remove the wrapper and use '${wrappedComponent}' directly, or merge the logic into '${wrappedComponent}' and rename it to '${componentName}'.`;
2260
+ if (!isConfigFile) {
2261
+ const lineNumber = content.substring(0, matchIndex).split('\n').length;
2262
+
2263
+ // Check if this is already reported
2264
+ const alreadyReported = violations.directSupabaseClient.some(v =>
2265
+ v.file === relativePath &&
2266
+ v.variable === variableName &&
2267
+ Math.abs(v.line - lineNumber) <= 2
2268
+ );
2269
+
2270
+ if (!alreadyReported) {
2271
+ violations.directSupabaseClient.push({
2272
+ file: relativePath,
2273
+ line: lineNumber,
2274
+ variable: variableName,
2275
+ table: tableName,
2276
+ reason: `Direct Supabase client usage detected. Variable '${variableName}' is created with createClient() and used for database queries. You MUST use useSecureSupabase() instead to ensure RLS policies and organisation context are enforced.`,
2277
+ recommendation: `Replace with: import { useSecureSupabase } from '@jmruthers/pace-core/rbac'; const ${variableName} = useSecureSupabase();`
2278
+ });
2279
+ }
2127
2280
  }
2281
+ }
2282
+ }
2283
+
2284
+ // Also check if file imports createClient but doesn't use useSecureSupabase
2285
+ if (hasCreateClientImport && hasCreateClientUsage && !usesSecureSupabase) {
2286
+ // Check if it's a config file (acceptable)
2287
+ const isConfigFile = /config|supabase|client/i.test(relativePath) &&
2288
+ (relativePath.includes('supabase.ts') ||
2289
+ relativePath.includes('supabase.js') ||
2290
+ relativePath.includes('client.ts') ||
2291
+ relativePath.includes('client.js'));
2292
+
2293
+ if (!isConfigFile) {
2294
+ // Check if createClient is used for queries (not just config)
2295
+ const hasDatabaseQueries = /\.from\s*\(/.test(content);
2128
2296
 
2129
- issues.push({
2130
- component: componentName,
2131
- wrappedComponent: wrappedComponent,
2132
- file: relativePath,
2133
- line: getLineNumber(content, match[0]),
2134
- reason: reason,
2135
- recommendation: recommendation
2136
- });
2297
+ if (hasDatabaseQueries) {
2298
+ violations.directSupabaseClient.push({
2299
+ file: relativePath,
2300
+ line: 1,
2301
+ variable: 'unknown',
2302
+ table: 'multiple',
2303
+ reason: 'File imports createClient from @supabase/supabase-js and performs database queries. You MUST use useSecureSupabase() instead to ensure RLS policies and organisation context are enforced.',
2304
+ recommendation: 'Replace createClient with: import { useSecureSupabase } from \'@jmruthers/pace-core/rbac\'; const supabase = useSecureSupabase();'
2305
+ });
2306
+ }
2137
2307
  }
2138
2308
  }
2139
- });
2140
-
2141
- return issues;
2142
- }
2143
-
2144
- // Get line number for a match
2145
- function getLineNumber(content, match) {
2146
- const lines = content.substring(0, content.indexOf(match)).split('\n');
2147
- return lines.length;
2148
- }
2149
-
2150
- // Generate report
2151
- function generateReport(allViolations, manifest) {
2152
- const totalRestricted = allViolations.restrictedImports.length;
2153
- const totalDuplicates =
2154
- allViolations.duplicateComponents.length +
2155
- allViolations.duplicateHooks.length +
2156
- allViolations.duplicateUtils.length;
2157
- const totalSuggestions = allViolations.suggestions.length;
2158
- const totalRbacAuth =
2159
- allViolations.customAuthCode.length +
2160
- allViolations.duplicateConfig.length +
2161
- allViolations.unprotectedPages.length +
2162
- allViolations.directSupabaseAuth.length;
2163
- const totalSetupIssues =
2164
- allViolations.providerSetupIssues.length +
2165
- allViolations.viteConfigIssues.length +
2166
- allViolations.routerSetupIssues.length;
2167
- const totalUnnecessaryWrappers = allViolations.unnecessaryWrappers.length;
2168
- const totalAppDiscovery = allViolations.appDiscoveryIssues.length;
2169
- const totalIssues = totalRestricted + totalDuplicates + totalSuggestions + totalRbacAuth + totalSetupIssues + totalUnnecessaryWrappers + totalAppDiscovery;
2170
-
2171
- console.log(`\n${colors.bold}${colors.cyan}═══════════════════════════════════════════════════════════${colors.reset}`);
2172
- console.log(`${colors.bold}${colors.cyan} pace-core Compliance Report${colors.reset}`);
2173
- console.log(`${colors.bold}${colors.cyan}═══════════════════════════════════════════════════════════${colors.reset}\n`);
2174
-
2175
- // Restricted Imports
2176
- if (totalRestricted > 0) {
2177
- console.log(`${colors.red}${colors.bold}❌ Restricted Imports Found: ${totalRestricted}${colors.reset}\n`);
2178
- allViolations.restrictedImports.forEach(({ module, reason, file, line }) => {
2179
- console.log(` ${colors.red}•${colors.reset} ${colors.yellow}${file}:${line}${colors.reset}`);
2180
- console.log(` Import: ${colors.cyan}${module}${colors.reset}`);
2181
- console.log(` ${colors.yellow}Reason:${colors.reset} ${reason}\n`);
2182
- });
2183
- } else {
2184
- console.log(`${colors.green}✅ No restricted imports found${colors.reset}\n`);
2185
- }
2186
-
2187
- // Duplicate Components
2188
- if (allViolations.duplicateComponents.length > 0) {
2189
- console.log(`${colors.red}${colors.bold}❌ Duplicate Components Found: ${allViolations.duplicateComponents.length}${colors.reset}\n`);
2190
- allViolations.duplicateComponents.forEach(({ component, file }) => {
2191
- console.log(` ${colors.red}•${colors.reset} ${colors.yellow}${file}${colors.reset}`);
2192
- console.log(` Component '${colors.cyan}${component}${colors.reset}' conflicts with pace-core component`);
2193
- console.log(` ${colors.yellow}Suggestion:${colors.reset} Use '${component}' from '@jmruthers/pace-core' instead\n`);
2194
- });
2195
- }
2196
-
2197
- // Duplicate Hooks
2198
- if (allViolations.duplicateHooks.length > 0) {
2199
- console.log(`${colors.red}${colors.bold}❌ Duplicate Hooks Found: ${allViolations.duplicateHooks.length}${colors.reset}\n`);
2200
- allViolations.duplicateHooks.forEach(({ hook, file }) => {
2201
- console.log(` ${colors.red}•${colors.reset} ${colors.yellow}${file}${colors.reset}`);
2202
- console.log(` Hook '${colors.cyan}${hook}${colors.reset}' conflicts with pace-core hook`);
2203
- console.log(` ${colors.yellow}Suggestion:${colors.reset} Use '${hook}' from '@jmruthers/pace-core' instead\n`);
2204
- });
2205
- }
2206
-
2207
- // Duplicate Utils
2208
- if (allViolations.duplicateUtils.length > 0) {
2209
- console.log(`${colors.red}${colors.bold}❌ Duplicate Utils Found: ${allViolations.duplicateUtils.length}${colors.reset}\n`);
2210
- allViolations.duplicateUtils.forEach(({ util, file }) => {
2211
- console.log(` ${colors.red}•${colors.reset} ${colors.yellow}${file}${colors.reset}`);
2212
- console.log(` Util '${colors.cyan}${util}${colors.reset}' conflicts with pace-core util`);
2213
- console.log(` ${colors.yellow}Suggestion:${colors.reset} Use '${util}' from '@jmruthers/pace-core' instead\n`);
2214
- });
2215
2309
  }
2216
2310
 
2217
- // Suggestions
2218
- if (totalSuggestions > 0) {
2219
- console.log(`${colors.yellow}${colors.bold}💡 Suggestions: ${totalSuggestions}${colors.reset}\n`);
2220
- const grouped = {};
2221
- allViolations.suggestions.forEach(s => {
2222
- if (!grouped[s.file]) grouped[s.file] = [];
2223
- grouped[s.file].push(s);
2224
- });
2225
- Object.entries(grouped).forEach(([file, suggestions]) => {
2226
- console.log(` ${colors.yellow}•${colors.reset} ${colors.yellow}${file}${colors.reset}`);
2227
- suggestions.forEach(s => {
2228
- console.log(` ${s.suggestion}\n`);
2229
- });
2230
- });
2231
- }
2232
-
2233
- // Unnecessary Wrappers
2234
- if (totalUnnecessaryWrappers > 0) {
2235
- console.log(`${colors.yellow}${colors.bold}⚠️ Unnecessary Wrappers: ${totalUnnecessaryWrappers}${colors.reset}\n`);
2236
- allViolations.unnecessaryWrappers.forEach(({ component, wrappedComponent, file, line, reason, recommendation }) => {
2237
- console.log(` ${colors.yellow}•${colors.reset} ${colors.yellow}${file}:${line}${colors.reset}`);
2238
- console.log(` Component: ${colors.cyan}${component}${colors.reset}`);
2239
- console.log(` Wraps: ${colors.cyan}${wrappedComponent}${colors.reset}`);
2240
- console.log(` ${colors.yellow}Issue:${colors.reset} ${reason}`);
2241
- if (recommendation) {
2242
- console.log(` ${colors.green}Recommendation:${colors.reset} ${recommendation}\n`);
2243
- } else {
2244
- console.log(` ${colors.green}Recommendation:${colors.reset} Remove the wrapper and use the component directly.\n`);
2245
- }
2246
- });
2247
- }
2311
+ // ============================================
2312
+ // Check for Deprecated useSecureDataAccess Usage
2313
+ // ============================================
2314
+ // Detect when consuming apps use useSecureDataAccess() with secureQuery/secureInsert/etc
2315
+ // This is deprecated - they should migrate to useSecureSupabase() instead
2316
+ // This helps identify code that needs migration before retiring the old API
2248
2317
 
2249
- // App Discovery Issues
2250
- if (totalAppDiscovery > 0) {
2251
- console.log(`\n${colors.bold}${colors.cyan}═══════════════════════════════════════════════════════════${colors.reset}`);
2252
- console.log(`${colors.bold}${colors.cyan} App Discovery Compliance${colors.reset}`);
2253
- console.log(`${colors.bold}${colors.cyan}═══════════════════════════════════════════════════════════${colors.reset}\n`);
2254
-
2255
- // Separate by type
2256
- const directQueries = allViolations.appDiscoveryIssues.filter(v => v.type === 'direct_table_query');
2257
- const hardcodedNames = allViolations.appDiscoveryIssues.filter(v => v.type === 'hardcoded_app_name');
2258
- const suggestions = allViolations.appDiscoveryIssues.filter(v => v.type === 'suggestion');
2318
+ // Skip Edge Functions - they run in Deno
2319
+ if (!isEdgeFunction) {
2320
+ // Check for useSecureDataAccess import
2321
+ const hasSecureDataAccessImport = /import.*useSecureDataAccess.*from\s+['"]@jmruthers\/pace-core['"]/.test(content) ||
2322
+ /import.*useSecureDataAccess.*from\s+['"]@jmruthers\/pace-core\/hooks['"]/.test(content);
2259
2323
 
2260
- if (directQueries.length > 0) {
2261
- console.log(`${colors.yellow}${colors.bold}⚠️ Direct rbac_apps Queries: ${directQueries.length}${colors.reset}\n`);
2262
- directQueries.forEach(({ file, line, reason, recommendation }) => {
2263
- console.log(` ${colors.yellow}•${colors.reset} ${colors.yellow}${file}:${line}${colors.reset}`);
2264
- console.log(` ${colors.yellow}Issue:${colors.reset} ${reason}`);
2265
- console.log(` ${colors.green}Fix:${colors.reset} ${recommendation}\n`);
2266
- });
2267
- }
2324
+ // Check for useSecureDataAccess hook usage
2325
+ const hasSecureDataAccessHook = /useSecureDataAccess\s*\(/.test(content);
2268
2326
 
2269
- if (hardcodedNames.length > 0) {
2270
- console.log(`${colors.yellow}${colors.bold}⚠️ Hardcoded App Names: ${hardcodedNames.length}${colors.reset}\n`);
2271
- hardcodedNames.forEach(({ file, line, appName, reason, recommendation }) => {
2272
- console.log(` ${colors.yellow}•${colors.reset} ${colors.yellow}${file}:${line}${colors.reset}`);
2273
- console.log(` App Name: ${colors.cyan}${appName}${colors.reset}`);
2274
- console.log(` ${colors.yellow}Issue:${colors.reset} ${reason}`);
2275
- console.log(` ${colors.green}Fix:${colors.reset} ${recommendation}\n`);
2276
- });
2277
- }
2327
+ // Check for deprecated secure methods
2328
+ const deprecatedMethods = [
2329
+ { name: 'secureQuery', operation: 'query' },
2330
+ { name: 'secureInsert', operation: 'insert' },
2331
+ { name: 'secureUpdate', operation: 'update' },
2332
+ { name: 'secureDelete', operation: 'delete' },
2333
+ { name: 'secureRpc', operation: 'RPC call' }
2334
+ ];
2278
2335
 
2279
- if (suggestions.length > 0) {
2280
- console.log(`${colors.cyan}${colors.bold}💡 Suggestions: ${suggestions.length}${colors.reset}\n`);
2281
- suggestions.forEach(({ file, reason, recommendation }) => {
2282
- console.log(` ${colors.cyan}•${colors.reset} ${colors.yellow}${file}${colors.reset}`);
2283
- console.log(` ${reason}`);
2284
- console.log(` ${colors.green}Recommendation:${colors.reset} ${recommendation}\n`);
2285
- });
2286
- }
2336
+ // Pattern to find destructured secure methods from useSecureDataAccess
2337
+ const destructurePattern = /(const|let)\s*\{[^}]*\b(secureQuery|secureInsert|secureUpdate|secureDelete|secureRpc)\b[^}]*\}\s*=\s*useSecureDataAccess\s*\(/g;
2287
2338
 
2288
- console.log(`${colors.cyan}Example Usage:${colors.reset}`);
2289
- console.log(` ${colors.green}const { data: apps } = await supabase.rpc('data_rbac_apps_list');${colors.reset}`);
2290
- console.log(` ${colors.green}const appNames = apps?.map(app => app.name) || [];${colors.reset}\n`);
2291
- } else {
2292
- console.log(`${colors.green}✅ App discovery compliance: Using data_rbac_apps_list RPC function${colors.reset}\n`);
2293
- }
2294
-
2295
- // RBAC/Auth Compliance Section
2296
- if (totalRbacAuth > 0) {
2297
- console.log(`\n${colors.bold}${colors.cyan}═══════════════════════════════════════════════════════════${colors.reset}`);
2298
- console.log(`${colors.bold}${colors.cyan} RBAC/Auth Compliance${colors.reset}`);
2299
- console.log(`${colors.bold}${colors.cyan}═══════════════════════════════════════════════════════════${colors.reset}\n`);
2339
+ // Pattern to find direct method calls (secureQuery(...), secureInsert(...), etc.)
2340
+ const methodCallPatterns = deprecatedMethods.map(method => ({
2341
+ name: method.name,
2342
+ operation: method.operation,
2343
+ pattern: new RegExp(`\\b${method.name}\\s*\\(`, 'g')
2344
+ }));
2300
2345
 
2301
- // Custom Auth/RBAC Code
2302
- if (allViolations.customAuthCode.length > 0) {
2303
- // Separate by severity
2304
- const errors = allViolations.customAuthCode.filter(v => !v.severity || v.severity === 'error');
2305
- const warnings = allViolations.customAuthCode.filter(v => v.severity === 'warning');
2306
- const info = allViolations.customAuthCode.filter(v => v.severity === 'info');
2307
-
2308
- if (errors.length > 0) {
2309
- console.log(`${colors.red}${colors.bold}❌ Custom Auth/RBAC Code Found: ${errors.length}${colors.reset}\n`);
2310
- errors.forEach(({ name, type, file, line, reason, replacement, example }) => {
2311
- console.log(` ${colors.red}•${colors.reset} ${colors.yellow}${file}:${line}${colors.reset}`);
2312
- console.log(` ${type}: ${colors.cyan}${name}${colors.reset}`);
2313
- console.log(` ${colors.yellow}Reason:${colors.reset} ${reason}`);
2314
- if (replacement) {
2315
- console.log(` ${colors.green}Fix:${colors.reset} ${replacement}`);
2316
- // Add example code if provided
2317
- if (example) {
2318
- console.log(` ${colors.cyan}Example:${colors.reset}`);
2319
- example.split('\n').forEach(line => {
2320
- console.log(` ${colors.green}${line}${colors.reset}`);
2321
- });
2322
- } else {
2323
- // Add example code for common cases
2324
- if (type === 'rbac query' && name.includes('rbac_user_profiles')) {
2325
- console.log(` ${colors.cyan}Example:${colors.reset}`);
2326
- console.log(` ${colors.green}import { useSecureSupabase } from '@jmruthers/pace-core/rbac';${colors.reset}`);
2327
- console.log(` ${colors.green}const supabase = useSecureSupabase();${colors.reset}`);
2328
- console.log(` ${colors.green}const { data } = await supabase.from('rbac_user_profiles').select('*');${colors.reset}`);
2329
- } else if (type === 'rbac query' && name.includes('rbac_user_login_history')) {
2330
- console.log(` ${colors.cyan}Example:${colors.reset}`);
2331
- console.log(` ${colors.green}import { useSecureSupabase } from '@jmruthers/pace-core/rbac';${colors.reset}`);
2332
- console.log(` ${colors.green}const supabase = useSecureSupabase();${colors.reset}`);
2333
- console.log(` ${colors.green}const { data } = await supabase.from('rbac_user_login_history').select('*').eq('user_id', userId);${colors.reset}`);
2334
- console.log(` ${colors.yellow}Note:${colors.reset} Login history is automatically tracked by UnifiedAuthProvider.`);
2335
- console.log(` ${colors.yellow} ${colors.reset} Queries should use useSecureSupabase to ensure organisation context is enforced.`);
2336
- } else if (type === 'rbac query' && name.includes('rbac_user_units')) {
2337
- console.log(` ${colors.cyan}Example:${colors.reset}`);
2338
- console.log(` ${colors.green}import { useSecureSupabase } from '@jmruthers/pace-core/rbac';${colors.reset}`);
2339
- console.log(` ${colors.green}const supabase = useSecureSupabase();${colors.reset}`);
2340
- console.log(` ${colors.green}// For reading, use RPC:${colors.reset}`);
2341
- console.log(` ${colors.green}const { data } = await supabase.rpc('data_user_unit_get', {${colors.reset}`);
2342
- console.log(` ${colors.green} p_user_id: userId,${colors.reset}`);
2343
- console.log(` ${colors.green} p_event_id: eventId${colors.reset}`);
2344
- console.log(` ${colors.green}});${colors.reset}`);
2345
- } else if (type === 'rbac query' && name.includes('rbac_apps')) {
2346
- console.log(` ${colors.cyan}Example (app discovery):${colors.reset}`);
2347
- console.log(` ${colors.green}const { data: apps } = await supabase.rpc('data_rbac_apps_list');${colors.reset}`);
2348
- console.log(` ${colors.yellow}Note:${colors.reset} Use data_rbac_apps_list RPC function for dynamic app discovery instead of querying rbac_apps directly.`);
2349
- } else if (type === 'rbac query' && (name.includes('rbac_app_pages') || name.includes('rbac_page_permissions'))) {
2350
- console.log(` ${colors.cyan}Example (admin operations):${colors.reset}`);
2351
- console.log(` ${colors.green}import { useSecureSupabase } from '@jmruthers/pace-core/rbac';${colors.reset}`);
2352
- console.log(` ${colors.green}const supabase = useSecureSupabase();${colors.reset}`);
2353
- console.log(` ${colors.green}const { data } = await supabase.from('${name.match(/rbac_\w+/)?.[0] || 'rbac_table'}').select('*');${colors.reset}`);
2354
- }
2355
- }
2346
+ // Check if file uses the deprecated hook
2347
+ if (hasSecureDataAccessImport || hasSecureDataAccessHook) {
2348
+ // Find all destructured methods
2349
+ let destructureMatch;
2350
+ const foundMethods = new Set();
2351
+
2352
+ while ((destructureMatch = destructurePattern.exec(content)) !== null) {
2353
+ const destructureText = destructureMatch[0];
2354
+ deprecatedMethods.forEach(method => {
2355
+ if (destructureText.includes(method.name)) {
2356
+ foundMethods.add(method.name);
2356
2357
  }
2357
- console.log('');
2358
2358
  });
2359
2359
  }
2360
2360
 
2361
- if (warnings.length > 0) {
2362
- console.log(`${colors.yellow}${colors.bold}⚠️ Warnings (acceptable but should use secure methods): ${warnings.length}${colors.reset}\n`);
2363
- warnings.forEach(({ name, type, file, line, reason, replacement, example }) => {
2364
- console.log(` ${colors.yellow}•${colors.reset} ${colors.yellow}${file}:${line}${colors.reset}`);
2365
- console.log(` ${type}: ${colors.cyan}${name}${colors.reset}`);
2366
- console.log(` ${colors.yellow}Note:${colors.reset} ${reason}`);
2367
- if (replacement) {
2368
- console.log(` ${colors.green}Recommendation:${colors.reset} ${replacement}`);
2361
+ // Find all method calls
2362
+ methodCallPatterns.forEach(({ name, operation, pattern }) => {
2363
+ let match;
2364
+ pattern.lastIndex = 0;
2365
+
2366
+ while ((match = pattern.exec(content)) !== null) {
2367
+ const matchIndex = match.index;
2368
+
2369
+ // Skip if in a line comment
2370
+ const lineStart = content.lastIndexOf('\n', matchIndex) + 1;
2371
+ const lineUpToMatch = content.substring(lineStart, matchIndex);
2372
+ const isInLineComment = /\/\/[^\n]*$/.test(lineUpToMatch);
2373
+
2374
+ if (isInLineComment) {
2375
+ continue;
2369
2376
  }
2370
- if (example) {
2371
- console.log(` ${colors.cyan}Example:${colors.reset}`);
2372
- example.split('\n').forEach(line => {
2373
- console.log(` ${colors.green}${line}${colors.reset}`);
2374
- });
2377
+
2378
+ // Check if this is from useSecureDataAccess (not from a different source)
2379
+ // Look backwards to see if it's from useSecureDataAccess destructuring
2380
+ const beforeMatch = content.substring(Math.max(0, matchIndex - 500), matchIndex);
2381
+ const isFromSecureDataAccess =
2382
+ /useSecureDataAccess\s*\(/.test(beforeMatch) ||
2383
+ /(const|let)\s*\{[^}]*\bsecure(Query|Insert|Update|Delete|Rpc)\b/.test(beforeMatch);
2384
+
2385
+ if (isFromSecureDataAccess) {
2386
+ foundMethods.add(name);
2387
+
2388
+ const lineNumber = content.substring(0, matchIndex).split('\n').length;
2389
+
2390
+ // Check if already reported
2391
+ const alreadyReported = violations.deprecatedSecureDataAccess.some(v =>
2392
+ v.file === relativePath &&
2393
+ v.method === name &&
2394
+ Math.abs(v.line - lineNumber) <= 2
2395
+ );
2396
+
2397
+ if (!alreadyReported) {
2398
+ violations.deprecatedSecureDataAccess.push({
2399
+ file: relativePath,
2400
+ line: lineNumber,
2401
+ method: name,
2402
+ operation: operation,
2403
+ reason: `Deprecated method '${name}' from useSecureDataAccess() detected. This API is being retired. Migrate to useSecureSupabase() instead.`,
2404
+ recommendation: getMigrationRecommendation(name, operation)
2405
+ });
2406
+ }
2375
2407
  }
2376
- console.log('');
2377
- });
2378
- }
2379
-
2380
- // Don't show info-level items (these are correct usage)
2381
- // They're tracked but not displayed to avoid noise
2382
- }
2383
-
2384
- // Duplicate Configurations
2385
- if (allViolations.duplicateConfig.length > 0) {
2386
- console.log(`${colors.red}${colors.bold}❌ Duplicate Configurations Found: ${allViolations.duplicateConfig.length}${colors.reset}\n`);
2387
- allViolations.duplicateConfig.forEach(({ type, file, count, reason }) => {
2388
- console.log(` ${colors.red}•${colors.reset} ${colors.yellow}${file}${colors.reset}`);
2389
- console.log(` Type: ${colors.cyan}${type}${colors.reset}${count ? ` (${count} instances)` : ''}`);
2390
- console.log(` ${colors.yellow}Reason:${colors.reset} ${reason}\n`);
2391
- });
2392
- }
2393
-
2394
- // Unprotected Pages
2395
- if (allViolations.unprotectedPages.length > 0) {
2396
- console.log(`${colors.red}${colors.bold}❌ Unprotected Pages Found: ${allViolations.unprotectedPages.length}${colors.reset}\n`);
2397
- allViolations.unprotectedPages.forEach(({ file, reason }) => {
2398
- console.log(` ${colors.red}•${colors.reset} ${colors.yellow}${file}${colors.reset}`);
2399
- console.log(` ${colors.yellow}Reason:${colors.reset} ${reason}\n`);
2400
- });
2401
- }
2402
-
2403
- // Direct Supabase Auth Usage
2404
- if (allViolations.directSupabaseAuth.length > 0) {
2405
- console.log(`${colors.red}${colors.bold}❌ Direct Supabase Auth Usage Found: ${allViolations.directSupabaseAuth.length}${colors.reset}\n`);
2406
- allViolations.directSupabaseAuth.forEach(({ file, line, reason, method, recommendation }) => {
2407
- console.log(` ${colors.red}•${colors.reset} ${colors.yellow}${file}:${line}${colors.reset}`);
2408
- console.log(` ${colors.yellow}Reason:${colors.reset} ${reason}`);
2409
- if (recommendation) {
2410
- console.log(` ${colors.green}Fix:${colors.reset} ${recommendation}\n`);
2411
- } else {
2412
- console.log(` ${colors.green}Fix:${colors.reset} Use ${colors.cyan}useUnifiedAuth${colors.reset} hook from @jmruthers/pace-core instead of direct ${method ? `supabase.auth.${method}()` : 'Supabase auth'} calls\n`);
2413
2408
  }
2414
2409
  });
2415
- }
2416
- } else {
2417
- console.log(`\n${colors.green}✅ RBAC/Auth compliance: All checks passed${colors.reset}\n`);
2418
- }
2419
-
2420
- // Setup/Configuration Issues Section
2421
- if (totalSetupIssues > 0) {
2422
- console.log(`\n${colors.bold}${colors.cyan}═══════════════════════════════════════════════════════════${colors.reset}`);
2423
- console.log(`${colors.bold}${colors.cyan} Setup & Configuration Issues${colors.reset}`);
2424
- console.log(`${colors.bold}${colors.cyan}═══════════════════════════════════════════════════════════${colors.reset}\n`);
2425
-
2426
- // Provider Setup Issues
2427
- if (allViolations.providerSetupIssues.length > 0) {
2428
- console.log(`${colors.red}${colors.bold}❌ Provider Setup Issues Found: ${allViolations.providerSetupIssues.length}${colors.reset}\n`);
2429
- allViolations.providerSetupIssues.forEach(({ file, line, type, provider, reason, recommendation }) => {
2430
- console.log(` ${colors.red}•${colors.reset} ${colors.yellow}${file}:${line}${colors.reset}`);
2431
- if (provider) {
2432
- console.log(` Missing Provider: ${colors.cyan}${provider}${colors.reset}`);
2433
- }
2434
- if (type) {
2435
- console.log(` Issue Type: ${colors.cyan}${type}${colors.reset}`);
2410
+
2411
+ // If we found the hook usage but haven't reported specific methods, add a general warning
2412
+ if (foundMethods.size === 0 && hasSecureDataAccessHook) {
2413
+ // Check if it's just imported but not used, or used in a way we didn't detect
2414
+ const hasAnySecureMethodCall = /secure(Query|Insert|Update|Delete|Rpc)\s*\(/.test(content);
2415
+
2416
+ if (hasAnySecureMethodCall) {
2417
+ violations.deprecatedSecureDataAccess.push({
2418
+ file: relativePath,
2419
+ line: 1,
2420
+ method: 'useSecureDataAccess',
2421
+ operation: 'general',
2422
+ reason: 'useSecureDataAccess() hook detected. This API is deprecated and will be retired. Migrate to useSecureSupabase() instead.',
2423
+ recommendation: 'Replace useSecureDataAccess() with useSecureSupabase() and use standard Supabase query builder API (.from(), .select(), etc.)'
2424
+ });
2436
2425
  }
2437
- console.log(` ${colors.yellow}Problem:${colors.reset} ${reason}`);
2438
- console.log(` ${colors.green}Fix:${colors.reset} ${recommendation}\n`);
2439
- });
2440
- }
2441
-
2442
- // Vite Configuration Issues
2443
- if (allViolations.viteConfigIssues.length > 0) {
2444
- console.log(`${colors.red}${colors.bold}❌ Vite Configuration Issues Found: ${allViolations.viteConfigIssues.length}${colors.reset}\n`);
2445
- allViolations.viteConfigIssues.forEach(({ file, line, type, reason, recommendation }) => {
2446
- const severity = type === 'recommendation' ? colors.yellow : colors.red;
2447
- const icon = type === 'recommendation' ? '💡' : '❌';
2448
- console.log(` ${severity}${icon}${colors.reset} ${colors.yellow}${file}:${line}${colors.reset}`);
2449
- console.log(` ${colors.yellow}Issue:${colors.reset} ${reason}`);
2450
- console.log(` ${colors.green}Fix:${colors.reset} ${recommendation}\n`);
2451
- });
2452
- }
2453
-
2454
- // Router Setup Issues
2455
- if (allViolations.routerSetupIssues.length > 0) {
2456
- console.log(`${colors.red}${colors.bold}❌ Router Setup Issues Found: ${allViolations.routerSetupIssues.length}${colors.reset}\n`);
2457
- allViolations.routerSetupIssues.forEach(({ file, line, type, reason, recommendation }) => {
2458
- console.log(` ${colors.red}•${colors.reset} ${colors.yellow}${file}:${line}${colors.reset}`);
2459
- console.log(` Issue Type: ${colors.cyan}${type}${colors.reset}`);
2460
- console.log(` ${colors.yellow}Problem:${colors.reset} ${reason}`);
2461
- console.log(` ${colors.green}Fix:${colors.reset} ${recommendation}\n`);
2462
- });
2426
+ }
2463
2427
  }
2464
- } else {
2465
- console.log(`\n${colors.green}✅ Setup & Configuration: All checks passed${colors.reset}\n`);
2466
2428
  }
2467
2429
 
2468
- // Summary
2469
- console.log(`${colors.bold}${colors.cyan}═══════════════════════════════════════════════════════════${colors.reset}`);
2470
- console.log(`${colors.bold}Summary:${colors.reset}`);
2471
- console.log(` Total Issues: ${totalIssues > 0 ? colors.red : colors.green}${totalIssues}${colors.reset}`);
2472
- console.log(` - Restricted Imports: ${totalRestricted > 0 ? colors.red : colors.green}${totalRestricted}${colors.reset}`);
2473
- console.log(` - Duplicate Components/Hooks/Utils: ${totalDuplicates > 0 ? colors.red : colors.green}${totalDuplicates}${colors.reset}`);
2474
- console.log(` - Suggestions: ${colors.yellow}${totalSuggestions}${colors.reset}`);
2475
- console.log(` - RBAC/Auth Issues: ${totalRbacAuth > 0 ? colors.red : colors.green}${totalRbacAuth}${colors.reset}`);
2476
- console.log(` - Setup/Configuration Issues: ${totalSetupIssues > 0 ? colors.red : colors.green}${totalSetupIssues}${colors.reset}`);
2477
- console.log(` - Unnecessary Wrappers: ${totalUnnecessaryWrappers > 0 ? colors.yellow : colors.green}${totalUnnecessaryWrappers}${colors.reset}`);
2478
- console.log(` - App Discovery Issues: ${totalAppDiscovery > 0 ? colors.yellow : colors.green}${totalAppDiscovery}${colors.reset}`);
2479
-
2480
- if (totalIssues === 0) {
2481
- console.log(`\n${colors.green}${colors.bold}✅ Excellent! Your codebase is fully compliant with pace-core standards.${colors.reset}\n`);
2482
- return 0;
2483
- } else {
2484
- console.log(`\n${colors.yellow}${colors.bold}⚠️ Please review the issues above and migrate to pace-core components/hooks/utils.${colors.reset}\n`);
2485
- return 1;
2486
- }
2430
+ return violations;
2487
2431
  }
2488
2432
 
2489
- // Recursively find source files
2490
- function findSourceFiles(dir, fileList = []) {
2491
- const ignoreDirs = ['node_modules', 'dist', 'build', '.next', 'coverage', '__tests__', '__mocks__'];
2492
- const ignoreFiles = /\.(test|spec)\.(ts|tsx|js|jsx)$/;
2493
- const sourceExtensions = /\.(ts|tsx|js|jsx)$/;
2494
-
2495
- try {
2496
- const items = fs.readdirSync(dir);
2433
+ /**
2434
+ * Compliance check module
2435
+ */
2436
+ const complianceCheck = {
2437
+ name: 'compliance',
2438
+ description: 'pace-core compliance checks (restricted imports, duplicates, auth/RBAC, etc.)',
2439
+ severity: 'error',
2440
+
2441
+ async run(context) {
2442
+ const { projectRoot, files, manifest: providedManifest } = context;
2497
2443
 
2498
- items.forEach(item => {
2499
- const fullPath = path.join(dir, item);
2500
- const stat = fs.statSync(fullPath);
2501
-
2502
- if (stat.isDirectory()) {
2503
- if (!ignoreDirs.includes(item) && !item.startsWith('.')) {
2504
- findSourceFiles(fullPath, fileList);
2505
- }
2506
- } else if (stat.isFile()) {
2507
- if (sourceExtensions.test(item) && !ignoreFiles.test(item)) {
2508
- fileList.push(fullPath);
2444
+ // Load manifest if not provided
2445
+ const manifest = providedManifest || loadManifest();
2446
+
2447
+ if (!files || files.length === 0) {
2448
+ return {
2449
+ issues: [],
2450
+ warnings: [],
2451
+ suggestions: [],
2452
+ violations: {
2453
+ restrictedImports: [],
2454
+ duplicateComponents: [],
2455
+ duplicateHooks: [],
2456
+ duplicateUtils: [],
2457
+ suggestions: [],
2458
+ customAuthCode: [],
2459
+ duplicateConfig: [],
2460
+ unprotectedPages: [],
2461
+ directSupabaseAuth: [],
2462
+ directSupabaseClient: [],
2463
+ deprecatedSecureDataAccess: [],
2464
+ providerSetupIssues: [],
2465
+ viteConfigIssues: [],
2466
+ routerSetupIssues: [],
2467
+ unnecessaryWrappers: [],
2468
+ appDiscoveryIssues: []
2509
2469
  }
2470
+ };
2471
+ }
2472
+
2473
+ // Aggregate all violations
2474
+ const allViolations = {
2475
+ restrictedImports: [],
2476
+ duplicateComponents: [],
2477
+ duplicateHooks: [],
2478
+ duplicateUtils: [],
2479
+ suggestions: [],
2480
+ customAuthCode: [],
2481
+ duplicateConfig: [],
2482
+ unprotectedPages: [],
2483
+ directSupabaseAuth: [],
2484
+ directSupabaseClient: [],
2485
+ deprecatedSecureDataAccess: [],
2486
+ providerSetupIssues: [],
2487
+ viteConfigIssues: [],
2488
+ routerSetupIssues: [],
2489
+ unnecessaryWrappers: [],
2490
+ appDiscoveryIssues: []
2491
+ };
2492
+
2493
+ // Scan all files
2494
+ for (const filePath of files) {
2495
+ try {
2496
+ const violations = scanFile(filePath, manifest, projectRoot);
2497
+
2498
+ // Aggregate violations
2499
+ Object.keys(allViolations).forEach(key => {
2500
+ if (violations[key] && Array.isArray(violations[key])) {
2501
+ allViolations[key].push(...violations[key]);
2502
+ }
2503
+ });
2504
+ } catch (error) {
2505
+ // Skip files with errors
2506
+ console.warn(`Error scanning ${filePath}: ${error.message}`);
2510
2507
  }
2511
- });
2512
- } catch (error) {
2513
- // Skip directories we can't read
2514
- }
2515
-
2516
- return fileList;
2517
- }
2518
-
2519
- // Main function
2520
- function main() {
2521
- const manifest = loadManifest();
2522
- const projectRoot = findProjectRoot();
2523
-
2524
- console.log(`${colors.cyan}Scanning project at: ${projectRoot}${colors.reset}`);
2525
-
2526
- // Find all TypeScript/JavaScript files (excluding node_modules, dist, etc.)
2527
- const files = findSourceFiles(projectRoot);
2528
-
2529
- console.log(`Found ${files.length} files to scan...\n`);
2530
-
2531
- // Scan all files
2532
- const allViolations = {
2533
- restrictedImports: [],
2534
- duplicateComponents: [],
2535
- duplicateHooks: [],
2536
- duplicateUtils: [],
2537
- suggestions: [],
2538
- customAuthCode: [],
2539
- duplicateConfig: [],
2540
- unprotectedPages: [],
2541
- directSupabaseAuth: [],
2542
- providerSetupIssues: [],
2543
- viteConfigIssues: [],
2544
- routerSetupIssues: [],
2545
- unnecessaryWrappers: [],
2546
- appDiscoveryIssues: []
2547
- };
2548
-
2549
- files.forEach(file => {
2550
- try {
2551
- const violations = scanFile(file, manifest);
2552
- allViolations.restrictedImports.push(...violations.restrictedImports);
2553
- allViolations.duplicateComponents.push(...violations.duplicateComponents);
2554
- allViolations.duplicateHooks.push(...violations.duplicateHooks);
2555
- allViolations.duplicateUtils.push(...violations.duplicateUtils);
2556
- allViolations.suggestions.push(...violations.suggestions);
2557
- allViolations.customAuthCode.push(...violations.customAuthCode);
2558
- allViolations.duplicateConfig.push(...violations.duplicateConfig);
2559
- allViolations.unprotectedPages.push(...violations.unprotectedPages);
2560
- allViolations.directSupabaseAuth.push(...violations.directSupabaseAuth);
2561
- allViolations.providerSetupIssues.push(...violations.providerSetupIssues);
2562
- allViolations.viteConfigIssues.push(...violations.viteConfigIssues);
2563
- allViolations.routerSetupIssues.push(...violations.routerSetupIssues);
2564
- allViolations.unnecessaryWrappers.push(...violations.unnecessaryWrappers);
2565
- allViolations.appDiscoveryIssues.push(...violations.appDiscoveryIssues);
2566
- } catch (error) {
2567
- console.error(`${colors.red}Error scanning ${file}: ${error.message}${colors.reset}`);
2568
2508
  }
2569
- });
2570
-
2571
- // Generate and display report
2572
- const exitCode = generateReport(allViolations, manifest);
2573
- process.exit(exitCode);
2574
- }
2575
-
2576
- // Run if called directly
2577
- if (require.main === module) {
2578
- try {
2579
- main();
2580
- } catch (error) {
2581
- console.error(`${colors.red}Error: ${error.message}${colors.reset}`);
2582
- console.error(error.stack);
2583
- process.exit(1);
2509
+
2510
+ // Convert violations to issues/warnings/suggestions format
2511
+ const issues = [
2512
+ ...allViolations.restrictedImports.map(v => ({
2513
+ type: 'restricted-import',
2514
+ file: v.file,
2515
+ line: v.line,
2516
+ message: `Restricted import: ${v.module} - ${v.reason}`,
2517
+ recommendation: `Use pace-core alternative instead`
2518
+ })),
2519
+ ...allViolations.duplicateComponents.map(v => ({
2520
+ type: 'duplicate-component',
2521
+ file: v.file,
2522
+ message: `Duplicate component: ${v.component}`,
2523
+ recommendation: `Use ${v.component} from '@jmruthers/pace-core' instead`
2524
+ })),
2525
+ ...allViolations.duplicateHooks.map(v => ({
2526
+ type: 'duplicate-hook',
2527
+ file: v.file,
2528
+ message: `Duplicate hook: ${v.hook}`,
2529
+ recommendation: `Use ${v.hook} from '@jmruthers/pace-core' instead`
2530
+ })),
2531
+ ...allViolations.duplicateUtils.map(v => ({
2532
+ type: 'duplicate-util',
2533
+ file: v.file,
2534
+ message: `Duplicate util: ${v.util}`,
2535
+ recommendation: `Use ${v.util} from '@jmruthers/pace-core' instead`
2536
+ })),
2537
+ ...allViolations.customAuthCode.filter(v => !v.severity || v.severity === 'error').map(v => ({
2538
+ type: 'custom-auth-code',
2539
+ file: v.file,
2540
+ line: v.line,
2541
+ message: `${v.type}: ${v.name} - ${v.reason}`,
2542
+ recommendation: v.replacement || 'Use pace-core APIs instead'
2543
+ })),
2544
+ ...allViolations.directSupabaseClient.map(v => ({
2545
+ type: 'direct-supabase-client',
2546
+ file: v.file,
2547
+ line: v.line,
2548
+ message: `Direct Supabase client usage: ${v.reason}`,
2549
+ recommendation: v.recommendation || 'Use useSecureSupabase() instead'
2550
+ })),
2551
+ ...allViolations.providerSetupIssues.map(v => ({
2552
+ type: 'provider-setup',
2553
+ file: v.file,
2554
+ line: v.line,
2555
+ message: v.issue || v.reason,
2556
+ recommendation: v.recommendation
2557
+ })),
2558
+ ...allViolations.viteConfigIssues.map(v => ({
2559
+ type: 'vite-config',
2560
+ file: v.file,
2561
+ line: v.line,
2562
+ message: v.issue,
2563
+ recommendation: v.recommendation
2564
+ })),
2565
+ ...allViolations.routerSetupIssues.map(v => ({
2566
+ type: 'router-setup',
2567
+ file: v.file,
2568
+ line: v.line,
2569
+ message: v.issue,
2570
+ recommendation: v.recommendation
2571
+ }))
2572
+ ];
2573
+
2574
+ const warnings = [
2575
+ ...allViolations.customAuthCode.filter(v => v.severity === 'warning').map(v => ({
2576
+ type: 'custom-auth-code',
2577
+ file: v.file,
2578
+ line: v.line,
2579
+ message: `${v.type}: ${v.name} - ${v.reason}`,
2580
+ recommendation: v.replacement || 'Consider using pace-core APIs'
2581
+ })),
2582
+ ...allViolations.directSupabaseAuth.map(v => ({
2583
+ type: 'direct-supabase-auth',
2584
+ file: v.file,
2585
+ line: v.line,
2586
+ message: `Direct Supabase auth usage: ${v.reason}`,
2587
+ recommendation: v.recommendation || 'Use useUnifiedAuth() instead'
2588
+ })),
2589
+ ...allViolations.deprecatedSecureDataAccess.map(v => ({
2590
+ type: 'deprecated-api',
2591
+ file: v.file,
2592
+ line: v.line,
2593
+ message: `Deprecated API: ${v.method} - ${v.reason}`,
2594
+ recommendation: v.recommendation || 'Migrate to useSecureSupabase()'
2595
+ })),
2596
+ ...allViolations.unnecessaryWrappers.map(v => ({
2597
+ type: 'unnecessary-wrapper',
2598
+ file: v.file,
2599
+ line: v.line,
2600
+ message: `Unnecessary wrapper: ${v.component} wraps ${v.wrappedComponent}`,
2601
+ recommendation: v.recommendation || 'Remove wrapper and use component directly'
2602
+ })),
2603
+ ...allViolations.appDiscoveryIssues.filter(v => v.severity === 'warning').map(v => ({
2604
+ type: 'app-discovery',
2605
+ file: v.file,
2606
+ message: v.issue || v.reason,
2607
+ recommendation: v.recommendation
2608
+ }))
2609
+ ];
2610
+
2611
+ const suggestions = [
2612
+ ...allViolations.suggestions.map(v => ({
2613
+ type: 'suggestion',
2614
+ file: v.file,
2615
+ message: v.suggestion
2616
+ })),
2617
+ ...allViolations.appDiscoveryIssues.filter(v => v.severity === 'info').map(v => ({
2618
+ type: 'app-discovery',
2619
+ file: v.file,
2620
+ message: v.issue || v.reason,
2621
+ recommendation: v.recommendation
2622
+ }))
2623
+ ];
2624
+
2625
+ return {
2626
+ issues,
2627
+ warnings,
2628
+ suggestions,
2629
+ violations: allViolations
2630
+ };
2584
2631
  }
2585
- }
2586
-
2587
- module.exports = { main, scanFile, generateReport };
2632
+ };
2588
2633
 
2634
+ module.exports = complianceCheck;