@jmruthers/pace-core 0.6.4 → 0.6.6

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 (387) hide show
  1. package/CHANGELOG.md +104 -0
  2. package/README.md +5 -403
  3. package/core-usage-manifest.json +93 -0
  4. package/cursor-rules/00-pace-core-compliance.mdc +128 -26
  5. package/cursor-rules/01-standards-compliance.mdc +49 -8
  6. package/cursor-rules/02-project-structure.mdc +6 -0
  7. package/cursor-rules/03-solid-principles.mdc +2 -0
  8. package/cursor-rules/04-testing-standards.mdc +2 -0
  9. package/cursor-rules/05-bug-reports-and-features.mdc +2 -0
  10. package/cursor-rules/06-code-quality.mdc +2 -0
  11. package/cursor-rules/07-tech-stack-compliance.mdc +2 -0
  12. package/cursor-rules/08-markup-quality.mdc +52 -27
  13. package/cursor-rules/09-rbac-compliance.mdc +462 -0
  14. package/cursor-rules/10-error-handling-patterns.mdc +179 -0
  15. package/cursor-rules/11-performance-optimization.mdc +169 -0
  16. package/cursor-rules/12-ci-cd-integration.mdc +150 -0
  17. package/dist/{AuthService-Cb34EQs3.d.ts → AuthService-DmfO5rGS.d.ts} +10 -0
  18. package/dist/{DataTable-BMRU8a1j.d.ts → DataTable-2N_tqbfq.d.ts} +1 -1
  19. package/dist/DataTable-LRJL4IRV.js +15 -0
  20. package/dist/{PublicPageProvider-DEMpysFR.d.ts → PublicPageProvider-BBH6Vqg7.d.ts} +72 -139
  21. package/dist/UnifiedAuthProvider-ZT6TIGM7.js +7 -0
  22. package/dist/api-Y4MQWOFW.js +4 -0
  23. package/dist/audit-MYQXYZFU.js +3 -0
  24. package/dist/{chunk-J36DSWQK.js → chunk-2HGJFNAH.js} +8 -28
  25. package/dist/{chunk-OEWDTMG7.js → chunk-3O3WHILE.js} +38 -121
  26. package/dist/{chunk-M43Y4SSO.js → chunk-3QC3KRHK.js} +1 -14
  27. package/dist/{chunk-DGUM43GV.js → chunk-3RG5ZIWI.js} +1 -4
  28. package/dist/{chunk-QXHPKYJV.js → chunk-4SXLQIZO.js} +1 -26
  29. package/dist/chunk-4T7OBVTU.js +62 -0
  30. package/dist/{chunk-E66EQZE6.js → chunk-6GLLNA6U.js} +3 -9
  31. package/dist/{chunk-ZSAAAMVR.js → chunk-6QYDGKQY.js} +1 -4
  32. package/dist/{chunk-NN6WWZ5U.js → chunk-7TYHROIV.js} +579 -563
  33. package/dist/{chunk-M7MPQISP.js → chunk-A55DK444.js} +9 -16
  34. package/dist/{chunk-63FOKYGO.js → chunk-AHU7G2R5.js} +2 -11
  35. package/dist/{chunk-L4OXEN46.js → chunk-BVP2BCJF.js} +2 -16
  36. package/dist/chunk-C7NSAPTL.js +1 -0
  37. package/dist/{chunk-YKRAFF5K.js → chunk-FENMYN2U.js} +73 -149
  38. package/dist/{chunk-AVMLPIM7.js → chunk-FTCRZOG2.js} +284 -432
  39. package/dist/{chunk-G37KK66H.js → chunk-FYHN4DD5.js} +60 -19
  40. package/dist/{chunk-VBXEHIUJ.js → chunk-HF6O3O37.js} +6 -88
  41. package/dist/{chunk-I6DAQMWX.js → chunk-LAZMKTTF.js} +930 -891
  42. package/dist/{chunk-5EC5MEWX.js → chunk-MAGBIDNS.js} +77 -222
  43. package/dist/chunk-MBADTM7L.js +64 -0
  44. package/dist/chunk-OHIK3MIO.js +994 -0
  45. package/dist/{chunk-6SOIHG6Z.js → chunk-S7DKJPLT.js} +115 -44
  46. package/dist/{chunk-FMUCXFII.js → chunk-SD6WQY43.js} +1 -5
  47. package/dist/{chunk-PWLANIRT.js → chunk-TTRFSOKR.js} +1 -7
  48. package/dist/{chunk-5DRSZLL2.js → chunk-UH3NTO3F.js} +1 -6
  49. package/dist/{chunk-FFQEQTNW.js → chunk-UIYSCEV7.js} +134 -45
  50. package/dist/{chunk-3LPHPB62.js → chunk-ZFYPMX46.js} +271 -87
  51. package/dist/{chunk-7JPAB3T5.js → chunk-ZS5VO5JB.js} +1989 -1283
  52. package/dist/components.d.ts +6 -6
  53. package/dist/components.js +57 -267
  54. package/dist/{database.generated-CzIvgcPu.d.ts → database.generated-CcnC_DRc.d.ts} +4795 -3691
  55. package/dist/eslint-rules/index.cjs +22 -0
  56. package/dist/eslint-rules/rules/compliance.cjs +348 -0
  57. package/dist/eslint-rules/rules/components.cjs +113 -0
  58. package/dist/eslint-rules/rules/imports.cjs +102 -0
  59. package/dist/eslint-rules/rules/rbac.cjs +790 -0
  60. package/dist/eslint-rules/utils/helpers.cjs +42 -0
  61. package/dist/eslint-rules/utils/manifest-loader.cjs +75 -0
  62. package/dist/hooks.d.ts +5 -5
  63. package/dist/hooks.js +62 -270
  64. package/dist/icons/index.d.ts +1 -0
  65. package/dist/icons/index.js +1 -0
  66. package/dist/index.d.ts +36 -26
  67. package/dist/index.js +87 -690
  68. package/dist/providers.d.ts +2 -2
  69. package/dist/providers.js +8 -35
  70. package/dist/rbac/eslint-rules.d.ts +46 -44
  71. package/dist/rbac/eslint-rules.js +7 -4
  72. package/dist/rbac/index.d.ts +124 -594
  73. package/dist/rbac/index.js +14 -207
  74. package/dist/styles/index.js +2 -12
  75. package/dist/theming/runtime.js +3 -19
  76. package/dist/{timezone-CHhWg6b4.d.ts → timezone-BZe_eUxx.d.ts} +175 -1
  77. package/dist/{types-CkbwOr4Y.d.ts → types-B-K_5VnO.d.ts} +4 -0
  78. package/dist/types-t9H8qKRw.d.ts +55 -0
  79. package/dist/types.d.ts +1 -1
  80. package/dist/types.js +7 -94
  81. package/dist/{usePublicRouteParams-i3qtoBgg.d.ts → usePublicRouteParams-COZ28Mvq.d.ts} +9 -9
  82. package/dist/utils.d.ts +24 -117
  83. package/dist/utils.js +54 -392
  84. package/docs/README.md +16 -6
  85. package/docs/api/README.md +4 -402
  86. package/docs/api/modules.md +454 -930
  87. package/docs/api-reference/components.md +3 -1
  88. package/docs/api-reference/deprecated.md +31 -6
  89. package/docs/api-reference/rpc-functions.md +78 -3
  90. package/docs/best-practices/accessibility.md +6 -3
  91. package/docs/getting-started/cursor-rules.md +3 -23
  92. package/docs/getting-started/dependencies.md +650 -0
  93. package/docs/getting-started/installation-guide.md +20 -7
  94. package/docs/getting-started/quick-start.md +23 -12
  95. package/docs/implementation-guides/permission-enforcement.md +4 -0
  96. package/docs/rbac/MIGRATION_GUIDE.md +819 -0
  97. package/docs/rbac/RBAC_CONTRACT.md +724 -0
  98. package/docs/rbac/README.md +12 -3
  99. package/docs/rbac/edge-functions-guide.md +376 -0
  100. package/docs/rbac/secure-client-protection.md +0 -34
  101. package/docs/standards/00-pace-core-compliance.md +967 -0
  102. package/docs/standards/01-standards-compliance.md +188 -0
  103. package/docs/standards/02-project-structure.md +985 -0
  104. package/docs/standards/03-solid-principles.md +39 -0
  105. package/docs/standards/04-testing-standards.md +36 -0
  106. package/docs/standards/05-bug-reports-and-features.md +27 -0
  107. package/docs/standards/{04-code-style-standard.md → 06-code-quality.md} +2 -0
  108. package/docs/standards/07-tech-stack-compliance.md +30 -0
  109. package/docs/standards/08-markup-quality.md +345 -0
  110. package/docs/standards/{07-rbac-and-rls-standard.md → 09-rbac-compliance.md} +149 -54
  111. package/docs/standards/10-error-handling-patterns.md +401 -0
  112. package/docs/standards/11-performance-optimization.md +348 -0
  113. package/docs/standards/12-ci-cd-integration.md +370 -0
  114. package/docs/standards/ALIGNMENT_REVIEW_SUMMARY.md +192 -0
  115. package/docs/standards/README.md +62 -33
  116. package/docs/troubleshooting/organisation-context-setup.md +42 -19
  117. package/eslint-config-pace-core.cjs +20 -4
  118. package/package.json +31 -21
  119. package/scripts/audit/audit-compliance.cjs +1295 -0
  120. package/scripts/audit/audit-components.cjs +260 -0
  121. package/scripts/audit/audit-dependencies.cjs +395 -0
  122. package/scripts/audit/audit-rbac.cjs +954 -0
  123. package/scripts/audit/audit-standards.cjs +1268 -0
  124. package/scripts/audit/index.cjs +1898 -194
  125. package/scripts/install-cursor-rules.cjs +259 -8
  126. package/scripts/validate-master.js +1 -1
  127. package/src/__tests__/fixtures/supabase.ts +1 -1
  128. package/src/__tests__/helpers/__tests__/component-test-utils.test.tsx +1 -1
  129. package/src/__tests__/helpers/__tests__/optimized-test-setup.test.ts +1 -1
  130. package/src/__tests__/helpers/__tests__/supabaseMock.test.ts +1 -1
  131. package/src/__tests__/helpers/__tests__/test-utils.test.tsx +3 -3
  132. package/src/__tests__/helpers/component-test-utils.tsx +1 -1
  133. package/src/__tests__/helpers/supabaseMock.ts +2 -2
  134. package/src/__tests__/public-recipe-view.test.ts +38 -9
  135. package/src/components/Button/Button.tsx +5 -1
  136. package/src/components/ContextSelector/ContextSelector.tsx +42 -39
  137. package/src/components/DataTable/__tests__/keyboard.test.tsx +15 -2
  138. package/src/components/DataTable/components/DataTableBody.tsx +55 -31
  139. package/src/components/DataTable/components/DataTableCore.tsx +186 -13
  140. package/src/components/DataTable/components/DataTableLayout.tsx +30 -5
  141. package/src/components/DataTable/components/EditFields.tsx +23 -3
  142. package/src/components/DataTable/components/EditableRow.tsx +7 -2
  143. package/src/components/DataTable/components/ImportModal.tsx +4 -6
  144. package/src/components/DataTable/components/RowComponent.tsx +12 -0
  145. package/src/components/DataTable/components/ViewRowModal.tsx +4 -4
  146. package/src/components/DataTable/components/__tests__/ImportModal.test.tsx +455 -96
  147. package/src/components/DataTable/components/__tests__/ViewRowModal.test.tsx +122 -58
  148. package/src/components/DataTable/components/hooks/usePermissionTracking.ts +0 -4
  149. package/src/components/DataTable/core/DataTableContext.tsx +1 -1
  150. package/src/components/DataTable/hooks/__tests__/useDataTableState.test.ts +51 -47
  151. package/src/components/DataTable/hooks/useDataTablePermissions.ts +24 -21
  152. package/src/components/DataTable/hooks/useDataTableState.ts +125 -9
  153. package/src/components/DataTable/hooks/useTableColumns.ts +40 -2
  154. package/src/components/DataTable/hooks/useTableHandlers.ts +11 -0
  155. package/src/components/DataTable/types.ts +5 -0
  156. package/src/components/DateTimeField/DateTimeField.tsx +20 -20
  157. package/src/components/DateTimeField/README.md +5 -2
  158. package/src/components/Dialog/Dialog.test.tsx +361 -318
  159. package/src/components/Dialog/Dialog.tsx +1154 -323
  160. package/src/components/Dialog/index.ts +3 -3
  161. package/src/components/FileDisplay/FileDisplay.test.tsx +45 -2
  162. package/src/components/FileDisplay/FileDisplay.tsx +28 -22
  163. package/src/components/Form/Form.test.tsx +9 -10
  164. package/src/components/Form/Form.tsx +369 -9
  165. package/src/components/InactivityWarningModal/InactivityWarningModal.test.tsx +28 -28
  166. package/src/components/InactivityWarningModal/InactivityWarningModal.tsx +40 -54
  167. package/src/components/LoginForm/LoginForm.tsx +2 -2
  168. package/src/components/NavigationMenu/NavigationMenu.test.tsx +14 -13
  169. package/src/components/NavigationMenu/NavigationMenu.tsx +2 -2
  170. package/src/components/NavigationMenu/useNavigationFiltering.ts +11 -21
  171. package/src/components/PaceAppLayout/PaceAppLayout.test.tsx +6 -4
  172. package/src/components/PaceAppLayout/PaceAppLayout.tsx +30 -41
  173. package/src/components/PaceAppLayout/README.md +10 -9
  174. package/src/components/PaceAppLayout/test-setup.tsx +40 -31
  175. package/src/components/PaceLoginPage/PaceLoginPage.test.tsx +108 -61
  176. package/src/components/PaceLoginPage/PaceLoginPage.tsx +27 -3
  177. package/src/components/PasswordChange/PasswordChangeForm.test.tsx +61 -0
  178. package/src/components/PasswordChange/PasswordChangeForm.tsx +20 -13
  179. package/src/components/PublicLayout/PublicLayout.test.tsx +7 -3
  180. package/src/components/PublicLayout/PublicPageLayout.tsx +5 -8
  181. package/src/components/Select/Select.tsx +23 -21
  182. package/src/components/Select/types.ts +1 -1
  183. package/src/components/UserMenu/UserMenu.test.tsx +38 -6
  184. package/src/components/UserMenu/UserMenu.tsx +39 -34
  185. package/src/components/index.ts +3 -4
  186. package/src/eslint-rules/index.cjs +22 -0
  187. package/src/eslint-rules/rules/compliance.cjs +348 -0
  188. package/src/eslint-rules/rules/components.cjs +113 -0
  189. package/src/eslint-rules/rules/imports.cjs +102 -0
  190. package/src/eslint-rules/rules/rbac.cjs +790 -0
  191. package/src/eslint-rules/utils/helpers.cjs +42 -0
  192. package/src/eslint-rules/utils/manifest-loader.cjs +75 -0
  193. package/src/hooks/__tests__/hooks.integration.test.tsx +6 -8
  194. package/src/hooks/__tests__/useAppConfig.unit.test.ts +129 -67
  195. package/src/hooks/__tests__/usePublicEvent.simple.test.ts +149 -67
  196. package/src/hooks/__tests__/usePublicEvent.test.ts +149 -79
  197. package/src/hooks/__tests__/usePublicEvent.unit.test.ts +158 -109
  198. package/src/hooks/__tests__/useSessionDraft.test.ts +163 -0
  199. package/src/hooks/__tests__/useSessionRestoration.unit.test.tsx +10 -5
  200. package/src/hooks/public/usePublicEvent.ts +62 -190
  201. package/src/hooks/public/usePublicEventLogo.test.ts +70 -17
  202. package/src/hooks/public/usePublicEventLogo.ts +19 -9
  203. package/src/hooks/useAppConfig.ts +26 -24
  204. package/src/hooks/useEventTheme.test.ts +211 -233
  205. package/src/hooks/useEventTheme.ts +19 -28
  206. package/src/hooks/useEvents.ts +11 -7
  207. package/src/hooks/useKeyboardShortcuts.ts +1 -1
  208. package/src/hooks/useOrganisationPermissions.ts +9 -11
  209. package/src/hooks/useOrganisations.ts +13 -7
  210. package/src/hooks/useQueryCache.ts +0 -1
  211. package/src/hooks/useSessionDraft.ts +380 -0
  212. package/src/hooks/useSessionRestoration.ts +3 -1
  213. package/src/icons/index.ts +27 -0
  214. package/src/index.ts +16 -1
  215. package/src/providers/OrganisationProvider.tsx +23 -14
  216. package/src/providers/services/EventServiceProvider.tsx +1 -24
  217. package/src/providers/services/UnifiedAuthProvider.tsx +5 -48
  218. package/src/providers/services/__tests__/UnifiedAuthProvider.integration.test.tsx +3 -0
  219. package/src/rbac/README.md +20 -20
  220. package/src/rbac/__tests__/adapters.comprehensive.test.tsx +7 -457
  221. package/src/rbac/__tests__/auth-rbac.e2e.test.tsx +33 -7
  222. package/src/rbac/adapters.tsx +7 -295
  223. package/src/rbac/api.test.ts +44 -56
  224. package/src/rbac/api.ts +10 -17
  225. package/src/rbac/cache-invalidation.ts +0 -1
  226. package/src/rbac/compliance/index.ts +10 -0
  227. package/src/rbac/compliance/pattern-detector.ts +553 -0
  228. package/src/rbac/compliance/runtime-compliance.ts +22 -0
  229. package/src/rbac/components/AccessDenied.tsx +150 -0
  230. package/src/rbac/components/NavigationGuard.tsx +12 -20
  231. package/src/rbac/components/PagePermissionGuard.tsx +4 -24
  232. package/src/rbac/components/__tests__/NavigationGuard.test.tsx +21 -8
  233. package/src/rbac/components/index.ts +3 -41
  234. package/src/rbac/eslint-rules.js +1 -1
  235. package/src/rbac/hooks/index.ts +0 -3
  236. package/src/rbac/hooks/permissions/index.ts +0 -3
  237. package/src/rbac/hooks/permissions/useAccessLevel.ts +4 -8
  238. package/src/rbac/hooks/usePermissions.ts +0 -3
  239. package/src/rbac/hooks/useRBAC.test.ts +21 -3
  240. package/src/rbac/hooks/useRBAC.ts +4 -3
  241. package/src/rbac/hooks/useResolvedScope.test.ts +57 -47
  242. package/src/rbac/hooks/useResolvedScope.ts +58 -140
  243. package/src/rbac/hooks/useResourcePermissions.test.ts +241 -60
  244. package/src/rbac/hooks/useResourcePermissions.ts +182 -63
  245. package/src/rbac/hooks/useRoleManagement.test.ts +65 -22
  246. package/src/rbac/hooks/useRoleManagement.ts +147 -19
  247. package/src/rbac/hooks/useSecureSupabase.ts +4 -8
  248. package/src/rbac/index.ts +7 -9
  249. package/src/rbac/permissions.ts +17 -17
  250. package/src/rbac/utils/contextValidator.ts +45 -7
  251. package/src/services/AuthService.ts +132 -23
  252. package/src/services/EventService.ts +4 -97
  253. package/src/services/InactivityService.ts +155 -58
  254. package/src/services/OrganisationService.ts +7 -44
  255. package/src/services/__tests__/OrganisationService.test.ts +26 -8
  256. package/src/services/base/BaseService.ts +0 -3
  257. package/src/styles/core.css +4 -0
  258. package/src/types/database.generated.ts +4733 -3809
  259. package/src/utils/__tests__/organisationContext.unit.test.ts +9 -10
  260. package/src/utils/context/organisationContext.test.ts +13 -28
  261. package/src/utils/context/organisationContext.ts +21 -52
  262. package/src/utils/dynamic/dynamicUtils.ts +1 -1
  263. package/src/utils/file-reference/index.ts +39 -15
  264. package/src/utils/formatting/formatDateTime.test.ts +3 -2
  265. package/src/utils/formatting/formatTime.test.ts +3 -2
  266. package/src/utils/google-places/loadGoogleMapsScript.ts +29 -4
  267. package/src/utils/index.ts +4 -1
  268. package/src/utils/persistence/__tests__/keyDerivation.test.ts +135 -0
  269. package/src/utils/persistence/__tests__/sensitiveFieldDetection.test.ts +123 -0
  270. package/src/utils/persistence/keyDerivation.ts +304 -0
  271. package/src/utils/persistence/sensitiveFieldDetection.ts +212 -0
  272. package/src/utils/security/secureStorage.ts +5 -5
  273. package/src/utils/storage/helpers.ts +3 -3
  274. package/src/utils/supabase/createBaseClient.ts +147 -0
  275. package/src/utils/timezone/timezone.test.ts +1 -2
  276. package/src/utils/timezone/timezone.ts +1 -1
  277. package/src/utils/validation/csrf.ts +4 -4
  278. package/cursor-rules/CHANGELOG.md +0 -119
  279. package/cursor-rules/README.md +0 -192
  280. package/dist/DataTable-E7YQZD7D.js +0 -175
  281. package/dist/DataTable-E7YQZD7D.js.map +0 -1
  282. package/dist/UnifiedAuthProvider-QPXO24B4.js +0 -18
  283. package/dist/UnifiedAuthProvider-QPXO24B4.js.map +0 -1
  284. package/dist/api-6LVZTHDS.js +0 -52
  285. package/dist/api-6LVZTHDS.js.map +0 -1
  286. package/dist/audit-V53FV5AG.js +0 -17
  287. package/dist/audit-V53FV5AG.js.map +0 -1
  288. package/dist/chunk-36LVWXB2.js +0 -227
  289. package/dist/chunk-36LVWXB2.js.map +0 -1
  290. package/dist/chunk-3LPHPB62.js.map +0 -1
  291. package/dist/chunk-5DRSZLL2.js.map +0 -1
  292. package/dist/chunk-5EC5MEWX.js.map +0 -1
  293. package/dist/chunk-63FOKYGO.js.map +0 -1
  294. package/dist/chunk-6SOIHG6Z.js.map +0 -1
  295. package/dist/chunk-7JPAB3T5.js.map +0 -1
  296. package/dist/chunk-ATKZM7RX.js +0 -2053
  297. package/dist/chunk-ATKZM7RX.js.map +0 -1
  298. package/dist/chunk-AVMLPIM7.js.map +0 -1
  299. package/dist/chunk-DGUM43GV.js.map +0 -1
  300. package/dist/chunk-E66EQZE6.js.map +0 -1
  301. package/dist/chunk-FFQEQTNW.js.map +0 -1
  302. package/dist/chunk-FMUCXFII.js.map +0 -1
  303. package/dist/chunk-G37KK66H.js.map +0 -1
  304. package/dist/chunk-I6DAQMWX.js.map +0 -1
  305. package/dist/chunk-J36DSWQK.js.map +0 -1
  306. package/dist/chunk-KQCRWDSA.js +0 -1
  307. package/dist/chunk-KQCRWDSA.js.map +0 -1
  308. package/dist/chunk-L4OXEN46.js.map +0 -1
  309. package/dist/chunk-LMC26NLJ.js +0 -84
  310. package/dist/chunk-LMC26NLJ.js.map +0 -1
  311. package/dist/chunk-M43Y4SSO.js.map +0 -1
  312. package/dist/chunk-M7MPQISP.js.map +0 -1
  313. package/dist/chunk-NN6WWZ5U.js.map +0 -1
  314. package/dist/chunk-OEWDTMG7.js.map +0 -1
  315. package/dist/chunk-PWLANIRT.js.map +0 -1
  316. package/dist/chunk-QXHPKYJV.js.map +0 -1
  317. package/dist/chunk-VBXEHIUJ.js.map +0 -1
  318. package/dist/chunk-YKRAFF5K.js.map +0 -1
  319. package/dist/chunk-ZSAAAMVR.js.map +0 -1
  320. package/dist/components.js.map +0 -1
  321. package/dist/contextValidator-OOPCLPZW.js +0 -9
  322. package/dist/contextValidator-OOPCLPZW.js.map +0 -1
  323. package/dist/eslint-rules/pace-core-compliance.cjs +0 -510
  324. package/dist/hooks.js.map +0 -1
  325. package/dist/index.js.map +0 -1
  326. package/dist/providers.js.map +0 -1
  327. package/dist/rbac/eslint-rules.js.map +0 -1
  328. package/dist/rbac/index.js.map +0 -1
  329. package/dist/styles/index.js.map +0 -1
  330. package/dist/theming/runtime.js.map +0 -1
  331. package/dist/types.js.map +0 -1
  332. package/dist/utils.js.map +0 -1
  333. package/docs/standards/01-architecture-standard.md +0 -44
  334. package/docs/standards/02-api-and-rpc-standard.md +0 -39
  335. package/docs/standards/03-component-standard.md +0 -32
  336. package/docs/standards/05-security-standard.md +0 -44
  337. package/docs/standards/06-testing-and-docs-standard.md +0 -29
  338. package/docs/standards/pace-core-compliance.md +0 -432
  339. package/scripts/audit/core/checks/accessibility.cjs +0 -197
  340. package/scripts/audit/core/checks/api-usage.cjs +0 -191
  341. package/scripts/audit/core/checks/bundle.cjs +0 -142
  342. package/scripts/audit/core/checks/compliance.cjs +0 -2706
  343. package/scripts/audit/core/checks/config.cjs +0 -54
  344. package/scripts/audit/core/checks/coverage.cjs +0 -84
  345. package/scripts/audit/core/checks/dependencies.cjs +0 -994
  346. package/scripts/audit/core/checks/documentation.cjs +0 -268
  347. package/scripts/audit/core/checks/environment.cjs +0 -116
  348. package/scripts/audit/core/checks/error-handling.cjs +0 -340
  349. package/scripts/audit/core/checks/forms.cjs +0 -172
  350. package/scripts/audit/core/checks/heuristics.cjs +0 -68
  351. package/scripts/audit/core/checks/hooks.cjs +0 -334
  352. package/scripts/audit/core/checks/imports.cjs +0 -244
  353. package/scripts/audit/core/checks/performance.cjs +0 -325
  354. package/scripts/audit/core/checks/routes.cjs +0 -117
  355. package/scripts/audit/core/checks/state.cjs +0 -130
  356. package/scripts/audit/core/checks/structure.cjs +0 -65
  357. package/scripts/audit/core/checks/style.cjs +0 -584
  358. package/scripts/audit/core/checks/testing.cjs +0 -122
  359. package/scripts/audit/core/checks/typescript.cjs +0 -61
  360. package/scripts/audit/core/scanner.cjs +0 -199
  361. package/scripts/audit/core/utils.cjs +0 -137
  362. package/scripts/audit/reporters/console.cjs +0 -151
  363. package/scripts/audit/reporters/json.cjs +0 -54
  364. package/scripts/audit/reporters/markdown.cjs +0 -124
  365. package/scripts/audit-consuming-app.cjs +0 -86
  366. package/src/eslint-rules/pace-core-compliance.cjs +0 -510
  367. package/src/eslint-rules/pace-core-compliance.js +0 -638
  368. package/src/rbac/components/EnhancedNavigationMenu.test.tsx +0 -555
  369. package/src/rbac/components/EnhancedNavigationMenu.tsx +0 -293
  370. package/src/rbac/components/NavigationProvider.test.tsx +0 -481
  371. package/src/rbac/components/NavigationProvider.tsx +0 -345
  372. package/src/rbac/components/PagePermissionProvider.test.tsx +0 -476
  373. package/src/rbac/components/PagePermissionProvider.tsx +0 -279
  374. package/src/rbac/components/PermissionEnforcer.tsx +0 -312
  375. package/src/rbac/components/RoleBasedRouter.tsx +0 -440
  376. package/src/rbac/components/SecureDataProvider.test.tsx +0 -543
  377. package/src/rbac/components/SecureDataProvider.tsx +0 -339
  378. package/src/rbac/components/__tests__/EnhancedNavigationMenu.test.tsx +0 -620
  379. package/src/rbac/components/__tests__/NavigationProvider.test.tsx +0 -726
  380. package/src/rbac/components/__tests__/PagePermissionProvider.test.tsx +0 -661
  381. package/src/rbac/components/__tests__/PermissionEnforcer.test.tsx +0 -881
  382. package/src/rbac/components/__tests__/RoleBasedRouter.test.tsx +0 -783
  383. package/src/rbac/components/__tests__/SecureDataProvider.fixed.test.tsx +0 -645
  384. package/src/rbac/components/__tests__/SecureDataProvider.test.tsx +0 -659
  385. package/src/rbac/hooks/permissions/useCachedPermissions.ts +0 -79
  386. package/src/rbac/hooks/permissions/useHasAllPermissions.ts +0 -90
  387. package/src/rbac/hooks/permissions/useHasAnyPermission.ts +0 -90
@@ -1,325 +0,0 @@
1
- #!/usr/bin/env node
2
-
3
- /**
4
- * Performance Check Module
5
- * @package @jmruthers/pace-core
6
- * @module Audit/Checks/Performance
7
- *
8
- * Checks for:
9
- * - Missing React.memo on expensive components
10
- * - Missing useMemo/useCallback for expensive computations
11
- * - Unnecessary re-renders
12
- * - Large inline objects/functions in JSX props
13
- * - Missing key props in lists
14
- */
15
-
16
- const fs = require('fs');
17
- const { getRelativePath, getLineNumber } = require('../utils.cjs');
18
-
19
- const performanceCheck = {
20
- name: 'performance',
21
- description: 'Performance anti-patterns (missing memoization, unnecessary re-renders)',
22
- severity: 'warning',
23
-
24
- async run(context) {
25
- const { projectRoot, files } = context;
26
- const issues = [];
27
- const warnings = [];
28
- const suggestions = [];
29
-
30
- if (!files || files.length === 0) {
31
- return { issues, warnings, suggestions };
32
- }
33
-
34
- for (const filePath of files) {
35
- try {
36
- // Only check React component files
37
- if (!filePath.match(/\.(tsx|jsx)$/)) {
38
- continue;
39
- }
40
-
41
- const content = fs.readFileSync(filePath, 'utf8');
42
- const relativePath = getRelativePath(filePath, projectRoot);
43
- const normalizedPath = relativePath.replace(/\\/g, '/');
44
-
45
- // Skip root-level src directory - in pace-core repository, this is a demo/showcase app
46
- // Note: We DO check packages/core/ files because performance issues (like missing key props) are real issues that should be fixed
47
- const isRootSrc = normalizedPath.startsWith('src/') && !normalizedPath.includes('packages/');
48
- if (isRootSrc) {
49
- continue; // Skip demo app files
50
- }
51
-
52
- // Skip scripts directory - utility scripts don't need performance validation
53
- const isScript = normalizedPath.startsWith('scripts/') || normalizedPath.includes('/scripts/');
54
- if (isScript) {
55
- continue; // Skip script files
56
- }
57
-
58
- // Skip examples directory - these are demo/example files, not production code
59
- // Examples are meant to show usage patterns, not be optimized for performance
60
- const isExample = normalizedPath.includes('/examples/') || normalizedPath.startsWith('examples/');
61
- if (isExample) {
62
- continue; // Skip example files
63
- }
64
-
65
- // Skip test files - test code doesn't need performance optimization
66
- const isTestFile = normalizedPath.includes('.test.') ||
67
- normalizedPath.includes('.spec.') ||
68
- normalizedPath.includes('test-setup') ||
69
- normalizedPath.includes('__tests__');
70
- if (isTestFile) {
71
- continue; // Skip test files
72
- }
73
-
74
- // For library code (packages/core/src), be more lenient with performance suggestions
75
- // Library components often use inline functions which are acceptable patterns
76
- const isLibraryCode = normalizedPath.includes('packages/core/src/');
77
-
78
- // Check for components that could benefit from React.memo
79
- // Note: For library components, React.memo should be used judiciously
80
- // We only suggest it for components that are likely to render frequently
81
- const componentPattern = /export\s+(default\s+)?(function|const)\s+(\w+)\s*[=\(]/g;
82
- let match;
83
- while ((match = componentPattern.exec(content)) !== null) {
84
- const componentName = match[3];
85
- const isMemoized = content.includes(`React.memo(${componentName})`) ||
86
- content.includes(`memo(${componentName})`) ||
87
- content.includes(`export default memo(${componentName})`);
88
-
89
- // Check if component receives props and is not memoized
90
- if (match[0].includes('props') || match[0].includes('{') || content.includes(`${componentName}({`)) {
91
- // Check if component has significant JSX (heuristic: more than 5 lines of JSX)
92
- const componentStart = match.index;
93
- const afterMatch = content.substring(componentStart);
94
- const jsxLines = (afterMatch.match(/<[A-Z]/g) || []).length;
95
-
96
- // Skip if component uses forwardRef (memoization handled differently)
97
- // Skip if component is a hook (starts with 'use')
98
- // Skip if component is very simple (less than 5 JSX elements)
99
- // Skip if component is already memoized
100
- if (jsxLines > 5 && !isMemoized && !content.includes('forwardRef') && !componentName.startsWith('use')) {
101
- // For library components, be more conservative - only suggest for components
102
- // that are likely to be in lists or render frequently
103
- // Skip if it's a page-level component (like *Page, *Layout, *Route)
104
- const isPageComponent = /Page|Layout|Route|Modal|Dialog/.test(componentName);
105
-
106
- // Only suggest memo for components that are likely to render frequently
107
- // (not page-level components, which typically render once per route)
108
- // For library code, skip React.memo suggestions entirely - memoization decisions
109
- // should be made by the consuming application based on their specific use case
110
- if (!isPageComponent && !isLibraryCode) {
111
- suggestions.push({
112
- type: 'missing-memo',
113
- file: relativePath,
114
- line: getLineNumber(content, match.index),
115
- message: `Component '${componentName}' receives props but is not memoized`,
116
- recommendation: `Consider wrapping with React.memo if the component renders frequently: export default React.memo(${componentName})`
117
- });
118
- }
119
- // For library code, skip React.memo suggestions - library components are often
120
- // controlled by parent components, and memoization should be decided by consumers
121
- }
122
- }
123
- }
124
-
125
- // Check for large inline objects/functions in JSX props
126
- // But exclude JSX children (which is standard React pattern)
127
- const jsxPropPattern = /<[A-Z]\w+\s+[^>]*\{[^}]{100,}[^>]*>/g;
128
- let jsxMatch;
129
- while ((jsxMatch = jsxPropPattern.exec(content)) !== null) {
130
- const matchText = jsxMatch[0];
131
-
132
- // Check if this is JSX children (element prop) or guard component props
133
- // Patterns to exclude (these are valid React patterns):
134
- // 1. <Route element={<Component />} /> - React Router pattern
135
- // 2. <GuardComponent fallback={<Component />} /> - Guard component pattern
136
- // 3. <Component>{children}</Component> - JSX children (but this won't match our pattern)
137
- // 4. Any prop that contains JSX elements (starts with <)
138
-
139
- const isRouteElement = /element\s*=\s*\{/.test(matchText);
140
- const isGuardProp = /(fallback|loading|error|children)\s*=\s*\{/.test(matchText);
141
- const hasJSXElement = /<[A-Z]\w+/.test(matchText);
142
-
143
- // Also check if it's a className utility call (like cn()) - not a performance issue
144
- const isUtilityCall = /cn\s*\(|clsx\s*\(|classnames\s*\(/.test(matchText);
145
-
146
- // Only flag if it's an actual inline object/function, not JSX children
147
- if (!isRouteElement && !isGuardProp && !hasJSXElement && !isUtilityCall) {
148
- warnings.push({
149
- type: 'inline-object-in-jsx',
150
- file: relativePath,
151
- line: getLineNumber(content, jsxMatch.index),
152
- message: 'Large inline object or function in JSX props detected',
153
- recommendation: 'Extract inline objects/functions to variables or useMemo/useCallback to prevent unnecessary re-renders. JSX children in props (like React Router element prop or guard component fallback prop) are standard React patterns and not performance issues.'
154
- });
155
- }
156
- }
157
-
158
- // Check for missing key props in lists
159
- // Only flag if JSX is returned, not data arrays
160
- const mapPattern = /\.map\s*\(\s*\([^)]+\)\s*=>/g;
161
- while ((match = mapPattern.exec(content)) !== null) {
162
- const afterMap = content.substring(match.index, match.index + 500);
163
- // Check if JSX is returned without key
164
- // Also check if it's actually returning JSX (not just data objects)
165
- const returnsJSX = afterMap.includes('return') && afterMap.includes('<');
166
-
167
- // Check for key in various formats: key=, key:, or inside React.cloneElement
168
- const hasKey = afterMap.includes('key=') ||
169
- /key\s*:/.test(afterMap) ||
170
- /React\.cloneElement\s*\([^,]+,\s*\{[^}]*key\s*:/.test(afterMap);
171
-
172
- // Check if it's a data transformation (returns object/array/primitive, not JSX)
173
- // Pattern 1: Returns a simple value like .map(([key, _]) => key)
174
- const returnsSimpleValue = /=>\s*[a-zA-Z_$][a-zA-Z0-9_$]*\s*;/.test(afterMap) && !afterMap.includes('<');
175
- // Pattern 2: Returns a data object without JSX
176
- const returnsDataObject = /return\s*\{[^<]*\}/.test(afterMap) && !afterMap.match(/return\s*\{[^<]*<[A-Z]/);
177
- // Pattern 3: Returns array element access like .map((subRow) => subRow.original)
178
- const returnsDataProperty = /=>\s*[a-zA-Z_$][a-zA-Z0-9_$]*\.[a-zA-Z_$][a-zA-Z0-9_$]*\s*[;}]/.test(afterMap) && !afterMap.includes('<');
179
-
180
- // Skip pace-core files for this check - library code has complex patterns that are hard to detect accurately
181
- const isPaceCorePackage = normalizedPath.includes('packages/core/') || normalizedPath.startsWith('packages/core/');
182
-
183
- if (returnsJSX && !hasKey && !returnsDataObject && !returnsSimpleValue && !returnsDataProperty && !isPaceCorePackage) {
184
- warnings.push({
185
- type: 'missing-key',
186
- file: relativePath,
187
- line: getLineNumber(content, match.index),
188
- message: 'Array map returns JSX without key prop',
189
- recommendation: 'Add a unique key prop to list items: {items.map(item => <Item key={item.id} ... />)}. Data array transformations (not JSX) do not need keys.'
190
- });
191
- }
192
- }
193
-
194
- // Check for expensive computations that could use useMemo
195
- const expensivePatterns = [
196
- /\.filter\([^)]*\)\.map\(/g, // filter().map() chains
197
- /\.sort\([^)]*\)\.map\(/g, // sort().map() chains
198
- /\.reduce\([^)]*\)/g // reduce operations
199
- ];
200
-
201
- expensivePatterns.forEach(pattern => {
202
- let expMatch;
203
- while ((expMatch = pattern.exec(content)) !== null) {
204
- // Check a larger context to see if it's already in useMemo or useCallback
205
- const beforeMatch = content.substring(Math.max(0, expMatch.index - 500), expMatch.index);
206
- const afterMatch = content.substring(expMatch.index, Math.min(content.length, expMatch.index + 200));
207
-
208
- // Check if it's already in useMemo or useCallback
209
- if (beforeMatch.includes('useMemo') || beforeMatch.includes('useCallback')) {
210
- continue;
211
- }
212
-
213
- // Check if it's inside a function definition that's static (like aggregateFn, cell renderer, etc.)
214
- // These are function definitions that are called by the library, not recalculated on every render
215
- const isStaticFunctionDefinition = /(aggregateFn|cell|header|footer|render|format|transform)\s*[:=]\s*\(/.test(beforeMatch);
216
- if (isStaticFunctionDefinition) {
217
- continue; // Skip static function definitions
218
- }
219
-
220
- // Check if it's inside a const/let declaration that's likely already memoized
221
- // Pattern: const x = useMemo(() => ... or const x = useCallback(() => ...
222
- const isInMemoizedConst = /const\s+\w+\s*=\s*(useMemo|useCallback)\s*\(/.test(beforeMatch);
223
- if (isInMemoizedConst) {
224
- continue;
225
- }
226
-
227
- // Check if it's part of a column definition (static, not recalculated)
228
- const isColumnDefinition = /(accessorKey|header|cell|aggregateFn|aggregateCell)\s*:/.test(beforeMatch);
229
- if (isColumnDefinition) {
230
- continue; // Skip column definitions - these are static
231
- }
232
-
233
- suggestions.push({
234
- type: 'expensive-computation',
235
- file: relativePath,
236
- line: getLineNumber(content, expMatch.index),
237
- message: 'Expensive computation detected that could benefit from useMemo',
238
- recommendation: 'Wrap expensive computations in useMemo to prevent recalculation on every render'
239
- });
240
- }
241
- });
242
-
243
- // Check for functions passed as props that could use useCallback
244
- // Only flag complex inline functions, not simple event handlers
245
- // For library code, be very lenient - inline functions are often acceptable patterns
246
- if (!isLibraryCode) {
247
- // Only check for consuming applications, not library code
248
- const functionPropPattern = /(onClick|onChange|onSubmit|onFocus|onBlur|onMouseEnter|onMouseLeave|onValueChange)\s*=\s*\{[^}]*=>/g;
249
- let funcMatch;
250
- while ((funcMatch = functionPropPattern.exec(content)) !== null) {
251
- const beforeMatch = content.substring(Math.max(0, funcMatch.index - 150), funcMatch.index);
252
- const afterMatch = content.substring(funcMatch.index, Math.min(content.length, funcMatch.index + 300));
253
-
254
- // Skip if already using useCallback or defined as const
255
- if (beforeMatch.includes('useCallback') || beforeMatch.includes('const ')) {
256
- continue;
257
- }
258
-
259
- // Extract the function body to check complexity
260
- // Handle both arrow functions with and without braces
261
- let funcBody = '';
262
- const arrowMatch = afterMatch.match(/=>\s*(\{?)([^}]*?)(\}?)/);
263
- if (arrowMatch) {
264
- if (arrowMatch[1] === '{') {
265
- // Multi-line function with braces - extract content between braces
266
- const braceMatch = afterMatch.match(/=>\s*\{([^}]+)\}/);
267
- if (braceMatch) {
268
- funcBody = braceMatch[1];
269
- }
270
- } else {
271
- // Single expression arrow function
272
- funcBody = arrowMatch[2];
273
- }
274
- }
275
-
276
- if (funcBody) {
277
- const trimmedBody = funcBody.trim();
278
-
279
- // Very short handlers are usually simple
280
- if (trimmedBody.length < 50) {
281
- continue;
282
- }
283
-
284
- // Check for simple setState patterns (even with object spread)
285
- const isSimpleSetState = /^\s*set\w+\s*\([^)]*\)\s*;?\s*$/.test(trimmedBody) ||
286
- /^\s*set\w+\s*\([^)]*prev\s*=>\s*\(\{[^}]*\}\)[^}]*\)\s*;?\s*$/.test(trimmedBody);
287
-
288
- // Check for simple function calls
289
- const isSimpleFunctionCall = /^\s*(handle\w+|on\w+|navigate|window\.|document\.)\s*\([^)]*\)\s*;?\s*$/.test(trimmedBody);
290
-
291
- // Check for simple conditional (single if/ternary)
292
- const isSimpleConditional = /^\s*(if\s*\([^)]+\)\s*\{[^}]{0,50}\}|[^?]+\?[^:]+:[^;]+;?)\s*$/.test(trimmedBody);
293
-
294
- // Check for simple value transformations (common in library components)
295
- // Pattern: (e) => handleFunc(e.target.value || undefined)
296
- // Pattern: (e) => handleFunc(e.target.value ? transform(e.target.value) : undefined)
297
- const isSimpleValueTransform = /^\s*\w+\s*\([^)]*\.(target|value|checked|selected)[^)]*\)\s*;?\s*$/.test(trimmedBody) ||
298
- /^\s*\w+\s*\([^)]*\?[^:]+:[^;]+\)\s*;?\s*$/.test(trimmedBody);
299
-
300
- // Skip simple handlers - they're fine as inline functions
301
- if (isSimpleSetState || isSimpleFunctionCall || isSimpleConditional || isSimpleValueTransform) {
302
- continue;
303
- }
304
- }
305
-
306
- suggestions.push({
307
- type: 'inline-function-prop',
308
- file: relativePath,
309
- line: getLineNumber(content, funcMatch.index),
310
- message: 'Complex inline function passed as prop',
311
- recommendation: 'Use useCallback to memoize complex functions passed as props to prevent child re-renders. Simple event handlers (single setState call or function invocation) are fine as inline functions.'
312
- });
313
- }
314
- }
315
- // For library code, skip this check entirely - inline functions are acceptable patterns
316
- } catch (error) {
317
- // Skip files with errors
318
- }
319
- }
320
-
321
- return { issues, warnings, suggestions };
322
- }
323
- };
324
-
325
- module.exports = performanceCheck;
@@ -1,117 +0,0 @@
1
- #!/usr/bin/env node
2
-
3
- /**
4
- * Route Protection Check Module
5
- * @package @jmruthers/pace-core
6
- * @module Audit/Checks/Routes
7
- *
8
- * Checks for:
9
- * - Routes without PagePermissionGuard
10
- * - Incorrect route nesting
11
- * - Missing route error boundaries
12
- */
13
-
14
- const fs = require('fs');
15
- const { getRelativePath, getLineNumber } = require('../utils.cjs');
16
-
17
- const routesCheck = {
18
- name: 'routes',
19
- description: 'Route protection (PagePermissionGuard, route nesting)',
20
- severity: 'error',
21
-
22
- async run(context) {
23
- const { projectRoot, files } = context;
24
- const issues = [];
25
- const warnings = [];
26
- const suggestions = [];
27
-
28
- if (!files || files.length === 0) {
29
- return { issues, warnings, suggestions };
30
- }
31
-
32
- for (const filePath of files) {
33
- try {
34
- const content = fs.readFileSync(filePath, 'utf8');
35
- const relativePath = getRelativePath(filePath, projectRoot);
36
- const normalizedPath = relativePath.replace(/\\/g, '/');
37
-
38
- // Skip pace-core package files - routes check is for consuming applications, not the library itself
39
- // Note: Library components (like ProtectedRoute, PaceAppLayout) ARE the route protection components
40
- // They don't need to use PagePermissionGuard - they provide the protection functionality
41
- const isPaceCorePackage = normalizedPath.includes('packages/core/') || normalizedPath.startsWith('packages/core/');
42
- if (isPaceCorePackage) {
43
- continue; // Skip library files (including examples)
44
- }
45
-
46
- // Skip root-level src directory - in pace-core repository, this is a demo/showcase app
47
- const isRootSrc = normalizedPath.startsWith('src/') && !normalizedPath.includes('packages/');
48
- if (isRootSrc) {
49
- continue; // Skip demo app files
50
- }
51
-
52
- // Skip scripts directory - utility scripts don't need route protection validation
53
- const isScript = normalizedPath.startsWith('scripts/') || normalizedPath.includes('/scripts/');
54
- if (isScript) {
55
- continue; // Skip script files
56
- }
57
-
58
- // Check for route definitions
59
- const routePatterns = [
60
- /<Route\s+path=["'][^"']+["']/g,
61
- /<Route\s+element\s*=/g,
62
- /createBrowserRouter\s*\(/g,
63
- /createRoutesFromElements/g
64
- ];
65
-
66
- const hasRoutes = routePatterns.some(pattern => pattern.test(content));
67
-
68
- if (hasRoutes) {
69
- // Check for PagePermissionGuard
70
- const hasPagePermissionGuard = content.includes('PagePermissionGuard') ||
71
- content.includes('from \'@jmruthers/pace-core/rbac\'');
72
-
73
- if (!hasPagePermissionGuard && !relativePath.includes('test') && !relativePath.includes('spec')) {
74
- issues.push({
75
- type: 'unprotected-route',
76
- file: relativePath,
77
- message: 'Route file found without PagePermissionGuard',
78
- recommendation: 'Wrap routes with PagePermissionGuard from @jmruthers/pace-core/rbac to enforce permissions'
79
- });
80
- }
81
-
82
- // Check for route nesting issues
83
- const routeCount = (content.match(/<Route/g) || []).length;
84
- if (routeCount > 0) {
85
- // Check if routes are properly nested
86
- const hasRoutesWrapper = content.includes('<Routes') || content.includes('<BrowserRouter');
87
- if (!hasRoutesWrapper) {
88
- warnings.push({
89
- type: 'route-nesting',
90
- file: relativePath,
91
- message: 'Route elements found but may not be wrapped in <Routes>',
92
- recommendation: 'Ensure all Route elements are wrapped in <Routes> component'
93
- });
94
- }
95
- }
96
- }
97
-
98
- // Check for error boundaries around routes
99
- if (hasRoutes && !content.includes('ErrorBoundary') && !content.includes('error-boundary')) {
100
- suggestions.push({
101
- type: 'missing-route-error-boundary',
102
- file: relativePath,
103
- message: 'Routes without error boundary',
104
- recommendation: 'Wrap routes with ErrorBoundary to catch and handle route errors gracefully'
105
- });
106
- }
107
-
108
- } catch (error) {
109
- // Skip files with errors
110
- }
111
- }
112
-
113
- return { issues, warnings, suggestions };
114
- }
115
- };
116
-
117
- module.exports = routesCheck;
@@ -1,130 +0,0 @@
1
- #!/usr/bin/env node
2
-
3
- /**
4
- * State Management Check Module
5
- * @package @jmruthers/pace-core
6
- * @module Audit/Checks/State
7
- *
8
- * Checks for:
9
- * - Improper state lifting
10
- * - Prop drilling (too many levels)
11
- * - Missing state normalization
12
- * - Unnecessary global state
13
- */
14
-
15
- const fs = require('fs');
16
- const { getRelativePath, getLineNumber } = require('../utils.cjs');
17
-
18
- const stateCheck = {
19
- name: 'state',
20
- description: 'State management patterns (prop drilling, state lifting)',
21
- severity: 'suggestion',
22
-
23
- async run(context) {
24
- const { projectRoot, files } = context;
25
- const issues = [];
26
- const warnings = [];
27
- const suggestions = [];
28
-
29
- if (!files || files.length === 0) {
30
- return { issues, warnings, suggestions };
31
- }
32
-
33
- for (const filePath of files) {
34
- try {
35
- // Only check React component files
36
- if (!filePath.match(/\.(tsx|jsx)$/)) {
37
- continue;
38
- }
39
-
40
- const content = fs.readFileSync(filePath, 'utf8');
41
- const relativePath = getRelativePath(filePath, projectRoot);
42
- const normalizedPath = relativePath.replace(/\\/g, '/');
43
-
44
- // Skip pace-core package files - state check is for consuming applications, not the library itself
45
- // Note: Library components are designed to have many props for configurability - this is not prop drilling
46
- const isPaceCorePackage = normalizedPath.includes('packages/core/') || normalizedPath.startsWith('packages/core/');
47
- if (isPaceCorePackage) {
48
- continue; // Skip library files
49
- }
50
-
51
- // Skip root-level src directory - in pace-core repository, this is a demo/showcase app
52
- const isRootSrc = normalizedPath.startsWith('src/') && !normalizedPath.includes('packages/');
53
- if (isRootSrc) {
54
- continue; // Skip demo app files
55
- }
56
-
57
- // Skip scripts directory - utility scripts don't need state management validation
58
- const isScript = normalizedPath.startsWith('scripts/') || normalizedPath.includes('/scripts/');
59
- if (isScript) {
60
- continue; // Skip script files
61
- }
62
-
63
- // Check for prop drilling (component with many props that are just passed through)
64
- const componentPattern = /(?:function|const)\s+(\w+)\s*[=\(]\s*\([^)]*\)/g;
65
- let match;
66
- while ((match = componentPattern.exec(content)) !== null) {
67
- const propsMatch = match[0].match(/\(([^)]+)\)/);
68
- if (propsMatch) {
69
- const props = propsMatch[1].split(',').map(p => p.trim()).filter(p => p);
70
-
71
- // Check if component has many props (potential prop drilling)
72
- if (props.length > 8) {
73
- suggestions.push({
74
- type: 'many-props',
75
- file: relativePath,
76
- line: getLineNumber(content, match.index),
77
- message: `Component has ${props.length} props - may indicate prop drilling`,
78
- recommendation: 'Consider using context or state management library if props are passed through multiple levels'
79
- });
80
- }
81
-
82
- // Check if props are just passed through to children
83
- const componentBody = content.substring(match.index, Math.min(content.length, match.index + 1000));
84
- const passThroughProps = props.filter(prop => {
85
- const propName = prop.split(':')[0].trim();
86
- return new RegExp(`<\\w+\\s+[^>]*${propName}=`, 'g').test(componentBody);
87
- });
88
-
89
- if (passThroughProps.length > props.length * 0.7 && props.length > 3) {
90
- suggestions.push({
91
- type: 'prop-drilling',
92
- file: relativePath,
93
- line: getLineNumber(content, match.index),
94
- message: 'Component appears to be passing most props through to children',
95
- recommendation: 'Consider lifting state up or using React Context to avoid prop drilling'
96
- });
97
- }
98
- }
99
- }
100
-
101
- // Check for unnecessary useState when data comes from props
102
- const useStatePattern = /const\s+\[(\w+),\s*set\w+\]\s*=\s*useState/g;
103
- let stateMatch;
104
- while ((stateMatch = useStatePattern.exec(content)) !== null) {
105
- const stateVar = stateMatch[1];
106
- const beforeState = content.substring(Math.max(0, stateMatch.index - 200), stateMatch.index);
107
-
108
- // Check if there's a prop with the same name
109
- const propPattern = new RegExp(`(?:props\\.|\\{\\s*${stateVar}\\s*\\})`, 'g');
110
- if (propPattern.test(beforeState)) {
111
- suggestions.push({
112
- type: 'unnecessary-state',
113
- file: relativePath,
114
- line: getLineNumber(content, stateMatch.index),
115
- message: `State variable '${stateVar}' may duplicate a prop`,
116
- recommendation: 'If state comes from props, consider using the prop directly or use derived state pattern'
117
- });
118
- }
119
- }
120
-
121
- } catch (error) {
122
- // Skip files with errors
123
- }
124
- }
125
-
126
- return { issues, warnings, suggestions };
127
- }
128
- };
129
-
130
- module.exports = stateCheck;
@@ -1,65 +0,0 @@
1
- #!/usr/bin/env node
2
-
3
- /**
4
- * File Structure Check Module
5
- * @package @jmruthers/pace-core
6
- * @module Audit/Checks/Structure
7
- */
8
-
9
- const fs = require('fs');
10
- const path = require('path');
11
-
12
- const structureCheck = {
13
- name: 'structure',
14
- description: 'File structure checks (required/recommended directories)',
15
- severity: 'error',
16
-
17
- async run(context) {
18
- const { projectRoot } = context;
19
- const issues = [];
20
- const warnings = [];
21
-
22
- // Skip structure checks if this is the pace-core repository itself
23
- // Structure checks are for consuming applications, not the library repository
24
- // Detect pace-core repository by checking if packages/core exists
25
- const packagesCorePath = path.join(projectRoot, 'packages', 'core');
26
- const isPaceCoreRepository = fs.existsSync(packagesCorePath);
27
-
28
- if (isPaceCoreRepository) {
29
- // This is the pace-core library repository, not a consuming app
30
- // Skip structure checks - the library has its own structure
31
- return { issues: [], warnings: [], suggestions: [] };
32
- }
33
-
34
- const requiredDirs = ['src'];
35
- const recommendedDirs = ['src/components', 'src/hooks', 'src/utils', 'src/pages'];
36
-
37
- requiredDirs.forEach(dir => {
38
- const dirPath = path.join(projectRoot, dir);
39
- if (!fs.existsSync(dirPath)) {
40
- issues.push({
41
- type: 'missing-directory',
42
- file: dir,
43
- message: `Required directory '${dir}' is missing`,
44
- recommendation: `Create the '${dir}' directory`
45
- });
46
- }
47
- });
48
-
49
- recommendedDirs.forEach(dir => {
50
- const dirPath = path.join(projectRoot, dir);
51
- if (!fs.existsSync(dirPath)) {
52
- warnings.push({
53
- type: 'missing-directory',
54
- file: dir,
55
- message: `Recommended directory '${dir}' is missing`,
56
- recommendation: `Consider creating the '${dir}' directory for better organization`
57
- });
58
- }
59
- });
60
-
61
- return { issues, warnings, suggestions: [] };
62
- }
63
- };
64
-
65
- module.exports = structureCheck;