@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,994 +0,0 @@
1
- #!/usr/bin/env node
2
-
3
- /**
4
- * Dependencies Check Module
5
- * @package @jmruthers/pace-core
6
- * @module Audit/Checks/Dependencies
7
- *
8
- * Checks for:
9
- * - Outdated pace-core version
10
- * - Security vulnerabilities
11
- * - Unused dependencies
12
- * - Missing peer dependencies
13
- * - Version mismatches
14
- * - Redundant dependencies (duplicates of pace-core deps)
15
- * - Missing dependencies (used but not declared)
16
- * - Misclassified dependencies (wrong section)
17
- * - Version mismatches across workspace
18
- * - Optional dependencies build configuration
19
- */
20
-
21
- const fs = require('fs');
22
- const path = require('path');
23
- const { getPackageInfo, findSourceFiles } = require('../utils.cjs');
24
-
25
- /**
26
- * Extract package name from import/require path
27
- */
28
- function extractPackageName(modulePath) {
29
- if (modulePath.startsWith('@')) {
30
- const parts = modulePath.split('/');
31
- if (parts.length >= 2) {
32
- return `${parts[0]}/${parts[1]}`;
33
- }
34
- } else {
35
- return modulePath.split('/')[0];
36
- }
37
- return null;
38
- }
39
-
40
- /**
41
- * Find all config files in the project
42
- */
43
- function findConfigFiles(projectRoot) {
44
- const configFiles = [];
45
- const configPatterns = [
46
- 'vite.config.{ts,js,mjs,cjs}',
47
- 'vitest.config.{ts,js,mjs,cjs}',
48
- 'tsconfig.json',
49
- '.eslintrc.{js,json,cjs}',
50
- 'eslint.config.{js,mjs,cjs}',
51
- 'tailwind.config.{ts,js}',
52
- 'postcss.config.{js,cjs}',
53
- 'babel.config.{js,json}',
54
- 'jest.config.{js,ts}',
55
- 'webpack.config.{js,ts}',
56
- 'rollup.config.{js,ts}',
57
- 'next.config.{js,mjs}',
58
- 'remix.config.{js,ts}',
59
- ];
60
-
61
- // Check root directory
62
- const rootFiles = fs.readdirSync(projectRoot);
63
- rootFiles.forEach(file => {
64
- configPatterns.forEach(pattern => {
65
- const regex = new RegExp('^' + pattern.replace(/\{.*?\}/g, '.*') + '$');
66
- if (regex.test(file)) {
67
- const fullPath = path.join(projectRoot, file);
68
- if (fs.existsSync(fullPath)) {
69
- configFiles.push(fullPath);
70
- }
71
- }
72
- });
73
- });
74
-
75
- return configFiles;
76
- }
77
-
78
- /**
79
- * Get pace-core peer dependencies
80
- */
81
- function getPaceCorePeerDeps(projectRoot) {
82
- try {
83
- // Try to find pace-core in node_modules
84
- const paceCorePath = path.join(projectRoot, 'node_modules', '@jmruthers', 'pace-core', 'package.json');
85
- if (fs.existsSync(paceCorePath)) {
86
- const paceCorePkg = JSON.parse(fs.readFileSync(paceCorePath, 'utf8'));
87
- return paceCorePkg.peerDependencies || {};
88
- }
89
- } catch (error) {
90
- // If we can't find it, return empty object
91
- }
92
- return {};
93
- }
94
-
95
- /**
96
- * Get pace-core dependencies (runtime dependencies provided by pace-core)
97
- */
98
- function getPaceCoreDeps(projectRoot) {
99
- try {
100
- // Try to find pace-core in node_modules
101
- const paceCorePath = path.join(projectRoot, 'node_modules', '@jmruthers', 'pace-core', 'package.json');
102
- if (fs.existsSync(paceCorePath)) {
103
- const paceCorePkg = JSON.parse(fs.readFileSync(paceCorePath, 'utf8'));
104
- return paceCorePkg.dependencies || {};
105
- }
106
- } catch (error) {
107
- // If we can't find it, return empty object
108
- }
109
- return {};
110
- }
111
-
112
- /**
113
- * Get pace-core package.json (for workspace checks)
114
- */
115
- function getPaceCorePackageJson(projectRoot) {
116
- try {
117
- // Try to find pace-core in node_modules
118
- const paceCorePath = path.join(projectRoot, 'node_modules', '@jmruthers', 'pace-core', 'package.json');
119
- if (fs.existsSync(paceCorePath)) {
120
- return JSON.parse(fs.readFileSync(paceCorePath, 'utf8'));
121
- }
122
- // Also try packages/core if we're in the workspace
123
- const packagesCorePath = path.join(projectRoot, 'packages', 'core', 'package.json');
124
- if (fs.existsSync(packagesCorePath)) {
125
- return JSON.parse(fs.readFileSync(packagesCorePath, 'utf8'));
126
- }
127
- } catch (error) {
128
- // If we can't find it, return null
129
- }
130
- return null;
131
- }
132
-
133
- /**
134
- * Get workspace root package.json (for monorepo checks)
135
- */
136
- function getWorkspaceRootPackageJson(projectRoot) {
137
- try {
138
- const rootPath = path.join(projectRoot, 'package.json');
139
- if (fs.existsSync(rootPath)) {
140
- return JSON.parse(fs.readFileSync(rootPath, 'utf8'));
141
- }
142
- } catch (error) {
143
- // If we can't find it, return null
144
- }
145
- return null;
146
- }
147
-
148
- /**
149
- * Check if a dependency is used in config files
150
- */
151
- function checkConfigFiles(configFiles, depName) {
152
- for (const configFile of configFiles) {
153
- try {
154
- const content = fs.readFileSync(configFile, 'utf8');
155
-
156
- // Check for direct imports/requires
157
- const importPatterns = [
158
- /from\s+['"]([^'"]+)['"]/g,
159
- /require\(['"]([^'"]+)['"]\)/g,
160
- /import\(['"]([^'"]+)['"]\)/g,
161
- ];
162
-
163
- for (const pattern of importPatterns) {
164
- let match;
165
- while ((match = pattern.exec(content)) !== null) {
166
- const modulePath = match[1];
167
- const pkgName = extractPackageName(modulePath);
168
- if (pkgName === depName) {
169
- return true;
170
- }
171
- }
172
- }
173
-
174
- // Check for string references (e.g., in optimizeDeps, plugins, etc.)
175
- // This is a heuristic - check if the package name appears as a string
176
- const depNameEscaped = depName.replace(/[.*+?^${}()|[\]\\]/g, '\\$&');
177
- const stringRefPattern = new RegExp(`['"]${depNameEscaped}['"]`, 'g');
178
- if (stringRefPattern.test(content)) {
179
- return true;
180
- }
181
- } catch (error) {
182
- // Skip files we can't read
183
- }
184
- }
185
- return false;
186
- }
187
-
188
- /**
189
- * Check if a dependency is used in CSS or other non-JS files
190
- */
191
- function checkNonJsFiles(projectRoot, depName) {
192
- const cssFiles = [];
193
- const cssExtensions = ['.css', '.scss', '.sass', '.less', '.styl'];
194
-
195
- try {
196
- const srcPath = path.join(projectRoot, 'src');
197
- if (fs.existsSync(srcPath)) {
198
- const walkDir = (dir) => {
199
- const files = fs.readdirSync(dir);
200
- files.forEach(file => {
201
- const fullPath = path.join(dir, file);
202
- const stat = fs.statSync(fullPath);
203
- if (stat.isDirectory() && !file.startsWith('.') && file !== 'node_modules') {
204
- walkDir(fullPath);
205
- } else if (stat.isFile()) {
206
- const ext = path.extname(file);
207
- if (cssExtensions.includes(ext)) {
208
- cssFiles.push(fullPath);
209
- }
210
- }
211
- });
212
- };
213
- walkDir(srcPath);
214
- }
215
- } catch (error) {
216
- // Skip if can't read
217
- }
218
-
219
- // Check CSS files for @import statements
220
- for (const cssFile of cssFiles) {
221
- try {
222
- const content = fs.readFileSync(cssFile, 'utf8');
223
- // Check for @import statements that might reference packages
224
- const importPattern = /@import\s+['"]([^'"]+)['"]/g;
225
- let match;
226
- while ((match = importPattern.exec(content)) !== null) {
227
- const modulePath = match[1];
228
- const pkgName = extractPackageName(modulePath);
229
- if (pkgName === depName) {
230
- return true;
231
- }
232
- }
233
- } catch (error) {
234
- // Skip files we can't read
235
- }
236
- }
237
-
238
- return false;
239
- }
240
-
241
- /**
242
- * Find all test files in the project
243
- */
244
- function findTestFiles(projectRoot) {
245
- const testFiles = [];
246
- const testPattern = /\.(test|spec)\.(ts|tsx|js|jsx)$/;
247
- const ignoreDirs = ['node_modules', 'dist', 'build', '.next', 'coverage'];
248
-
249
- const walkDir = (dir) => {
250
- try {
251
- const items = fs.readdirSync(dir);
252
- items.forEach(item => {
253
- const fullPath = path.join(dir, item);
254
- const stat = fs.statSync(fullPath);
255
-
256
- if (stat.isDirectory()) {
257
- if (!ignoreDirs.includes(item) && !item.startsWith('.')) {
258
- walkDir(fullPath);
259
- }
260
- } else if (stat.isFile() && testPattern.test(item)) {
261
- testFiles.push(fullPath);
262
- }
263
- });
264
- } catch (error) {
265
- // Skip directories we can't read
266
- }
267
- };
268
-
269
- try {
270
- const srcPath = path.join(projectRoot, 'src');
271
- if (fs.existsSync(srcPath)) {
272
- walkDir(srcPath);
273
- }
274
- } catch (error) {
275
- // Skip if can't read
276
- }
277
-
278
- return testFiles;
279
- }
280
-
281
- /**
282
- * Check if a dependency is used in package.json scripts
283
- */
284
- function checkPackageScripts(packageJson, depName) {
285
- if (!packageJson || !packageJson.scripts) {
286
- return false;
287
- }
288
-
289
- const scripts = packageJson.scripts;
290
- const scriptContent = Object.values(scripts).join(' ');
291
-
292
- // Check if the package name appears in any script
293
- // This is a simple check - package names in scripts are usually the command name
294
- const depNameEscaped = depName.replace(/[.*+?^${}()|[\]\\]/g, '\\$&');
295
- const scriptPattern = new RegExp(`\\b${depNameEscaped}\\b`, 'i');
296
-
297
- return scriptPattern.test(scriptContent);
298
- }
299
-
300
- /**
301
- * Check if a dependency is only used in test files
302
- */
303
- function isOnlyUsedInTests(projectRoot, depName, testFiles) {
304
- if (testFiles.length === 0) {
305
- return false;
306
- }
307
-
308
- // Check if it's used in any test file
309
- let foundInTests = false;
310
- for (const testFile of testFiles) {
311
- try {
312
- const content = fs.readFileSync(testFile, 'utf8');
313
- const depNameEscaped = depName.replace(/[.*+?^${}()|[\]\\]/g, '\\$&');
314
-
315
- // Check for imports
316
- const importPattern = new RegExp(`from\\s+['"]${depNameEscaped}`, 'g');
317
- const requirePattern = new RegExp(`require\\(['"]${depNameEscaped}`, 'g');
318
-
319
- if (importPattern.test(content) || requirePattern.test(content)) {
320
- foundInTests = true;
321
- break;
322
- }
323
- } catch (error) {
324
- // Skip files we can't read
325
- }
326
- }
327
-
328
- return foundInTests;
329
- }
330
-
331
- const dependenciesCheck = {
332
- name: 'dependencies',
333
- description: 'Dependency analysis (outdated versions, security, unused deps, redundant deps, misclassified deps, version mismatches)',
334
- severity: 'warning',
335
-
336
- async run(context) {
337
- const { projectRoot } = context;
338
- const issues = [];
339
- const warnings = [];
340
- const suggestions = [];
341
-
342
- const packageJson = getPackageInfo(projectRoot);
343
- if (!packageJson) {
344
- return { issues, warnings, suggestions };
345
- }
346
-
347
- // Skip dependency checks if this is the pace-core repository itself
348
- // Detect pace-core repository by checking if packages/core exists
349
- const packagesCorePath = path.join(projectRoot, 'packages', 'core');
350
- const isPaceCoreRepository = fs.existsSync(packagesCorePath);
351
-
352
- const allDeps = { ...packageJson.dependencies, ...packageJson.devDependencies };
353
- const devDeps = packageJson.devDependencies || {};
354
-
355
- // Get pace-core peer dependencies
356
- const paceCorePeerDeps = getPaceCorePeerDeps(projectRoot);
357
-
358
- // Get pace-core version (used in multiple checks)
359
- const paceCoreVersion = !isPaceCoreRepository ? allDeps['@jmruthers/pace-core'] : null;
360
-
361
- // Check for required React version (19.2.3) - do this early to ensure it always runs
362
- // This check MUST run for all consuming apps (not pace-core repository itself)
363
- if (!isPaceCoreRepository) {
364
- const requiredReactVersion = '19.2.3';
365
-
366
- // Read React versions from package.json - check both dependencies and devDependencies
367
- const reactVersion = (packageJson.dependencies && packageJson.dependencies.react) ||
368
- (packageJson.devDependencies && packageJson.devDependencies.react) ||
369
- null;
370
- const reactDomVersion = (packageJson.dependencies && packageJson.dependencies['react-dom']) ||
371
- (packageJson.devDependencies && packageJson.devDependencies['react-dom']) ||
372
- null;
373
-
374
- // Normalize version for comparison (remove ^, ~, >=, <=, etc.)
375
- const normalizeVersion = (v) => {
376
- if (!v || typeof v !== 'string') {
377
- return null;
378
- }
379
- // Remove range prefixes (^, ~, >=, <=, >, <, =)
380
- let normalized = v.replace(/^[\^~>=<]/, '');
381
- // Remove any whitespace
382
- normalized = normalized.trim();
383
- // Extract base version (e.g., "19.2.3" from "19.2.3" or "19.2.3-alpha.1")
384
- const match = normalized.match(/^(\d+\.\d+\.\d+)/);
385
- if (match && match[1]) {
386
- return match[1];
387
- }
388
- // Fallback: try to extract any version pattern
389
- const fallbackMatch = normalized.match(/(\d+\.\d+\.\d+)/);
390
- return fallbackMatch ? fallbackMatch[1] : null;
391
- };
392
-
393
- // Check React version
394
- if (reactVersion) {
395
- const normalizedReactVersion = normalizeVersion(reactVersion);
396
- const normalizedRequiredVersion = normalizeVersion(requiredReactVersion);
397
-
398
- // Check if versions match - always add issue if they don't match
399
- let versionMismatch = false;
400
-
401
- if (normalizedReactVersion && normalizedRequiredVersion) {
402
- // Both normalized successfully - compare normalized versions
403
- versionMismatch = normalizedReactVersion !== normalizedRequiredVersion;
404
- } else {
405
- // Normalization failed - do a simple string check
406
- const cleanReactVersion = reactVersion.replace(/^[\^~>=<]/, '').trim();
407
- const cleanRequiredVersion = requiredReactVersion.replace(/^[\^~>=<]/, '').trim();
408
- versionMismatch = cleanReactVersion !== cleanRequiredVersion && !cleanReactVersion.startsWith(requiredReactVersion);
409
- }
410
-
411
- if (versionMismatch) {
412
- issues.push({
413
- type: 'incorrect-react-version',
414
- file: 'package.json',
415
- message: `React version ${reactVersion} does not match required version ${requiredReactVersion}`,
416
- recommendation: `Install React ${requiredReactVersion}. Run: npm install react@${requiredReactVersion} react-dom@${requiredReactVersion}`
417
- });
418
- }
419
- } else {
420
- issues.push({
421
- type: 'missing-react',
422
- file: 'package.json',
423
- message: 'React is not installed but is required',
424
- recommendation: `Install React ${requiredReactVersion}. Run: npm install react@${requiredReactVersion} react-dom@${requiredReactVersion}`
425
- });
426
- }
427
-
428
- // Check react-dom version matches
429
- if (reactDomVersion) {
430
- const normalizedReactDomVersion = normalizeVersion(reactDomVersion);
431
- const normalizedRequiredVersion = normalizeVersion(requiredReactVersion);
432
-
433
- // Check if versions match - always add issue if they don't match
434
- let versionMismatch = false;
435
-
436
- if (normalizedReactDomVersion && normalizedRequiredVersion) {
437
- // Both normalized successfully - compare normalized versions
438
- versionMismatch = normalizedReactDomVersion !== normalizedRequiredVersion;
439
- } else {
440
- // Normalization failed - do a simple string check
441
- const cleanReactDomVersion = reactDomVersion.replace(/^[\^~>=<]/, '').trim();
442
- const cleanRequiredVersion = requiredReactVersion.replace(/^[\^~>=<]/, '').trim();
443
- versionMismatch = cleanReactDomVersion !== cleanRequiredVersion && !cleanReactDomVersion.startsWith(requiredReactVersion);
444
- }
445
-
446
- if (versionMismatch) {
447
- issues.push({
448
- type: 'incorrect-react-dom-version',
449
- file: 'package.json',
450
- message: `react-dom version ${reactDomVersion} does not match required version ${requiredReactVersion}`,
451
- recommendation: `Install react-dom ${requiredReactVersion}. Run: npm install react-dom@${requiredReactVersion}`
452
- });
453
- }
454
- } else if (reactVersion) {
455
- // If react is installed but react-dom is not, that's also an issue
456
- issues.push({
457
- type: 'missing-react-dom',
458
- file: 'package.json',
459
- message: 'react-dom is not installed but is required',
460
- recommendation: `Install react-dom ${requiredReactVersion}. Run: npm install react-dom@${requiredReactVersion}`
461
- });
462
- }
463
- }
464
-
465
- // Check pace-core version (skip if this is the pace-core repository itself)
466
- if (!isPaceCoreRepository) {
467
- if (paceCoreVersion) {
468
- // Check if version is outdated (simplified - would need to check npm registry)
469
- if (paceCoreVersion.startsWith('^') || paceCoreVersion.startsWith('~')) {
470
- // Version range - this is fine
471
- } else if (paceCoreVersion.match(/^\d+\.\d+\.\d+$/)) {
472
- // Exact version - suggest using range
473
- suggestions.push({
474
- type: 'exact-version',
475
- file: 'package.json',
476
- message: `pace-core is pinned to exact version ${paceCoreVersion}`,
477
- recommendation: 'Consider using a version range (^ or ~) to receive patch/minor updates automatically'
478
- });
479
- }
480
- } else {
481
- issues.push({
482
- type: 'missing-pace-core',
483
- file: 'package.json',
484
- message: '@jmruthers/pace-core is not in dependencies',
485
- recommendation: 'Add @jmruthers/pace-core to your dependencies'
486
- });
487
- }
488
- }
489
-
490
- // Collect all files to scan
491
- // Skip unused dependency checks if this is the pace-core repository itself
492
- // The library's dependencies are used in packages/core/src/, not in the root src/
493
- const sourceFiles = [];
494
- if (!isPaceCoreRepository) {
495
- try {
496
- const srcPath = path.join(projectRoot, 'src');
497
- if (fs.existsSync(srcPath)) {
498
- sourceFiles.push(...findSourceFiles(srcPath));
499
- }
500
- } catch (error) {
501
- // Skip if can't read source files
502
- }
503
- }
504
-
505
- // Also scan config files
506
- const configFiles = findConfigFiles(projectRoot);
507
-
508
- // Find test files separately for better analysis
509
- const testFiles = !isPaceCoreRepository ? findTestFiles(projectRoot) : [];
510
-
511
- // Build set of imported packages from source files (runtime)
512
- const importedPackages = new Set();
513
- // Build set of packages used only in tests
514
- const testOnlyPackages = new Set();
515
-
516
- // Scan source files (runtime code)
517
- sourceFiles.forEach(filePath => {
518
- try {
519
- const content = fs.readFileSync(filePath, 'utf8');
520
- // Check for ES6 imports
521
- const importPattern = /from\s+['"]([^'"]+)['"]/g;
522
- let match;
523
- while ((match = importPattern.exec(content)) !== null) {
524
- const modulePath = match[1];
525
- const pkgName = extractPackageName(modulePath);
526
- if (pkgName) {
527
- importedPackages.add(pkgName);
528
- }
529
- }
530
- // Check for require() statements
531
- const requirePattern = /require\(['"]([^'"]+)['"]\)/g;
532
- while ((match = requirePattern.exec(content)) !== null) {
533
- const modulePath = match[1];
534
- const pkgName = extractPackageName(modulePath);
535
- if (pkgName) {
536
- importedPackages.add(pkgName);
537
- }
538
- }
539
- // Check for dynamic imports
540
- const dynamicImportPattern = /import\(['"]([^'"]+)['"]\)/g;
541
- while ((match = dynamicImportPattern.exec(content)) !== null) {
542
- const modulePath = match[1];
543
- const pkgName = extractPackageName(modulePath);
544
- if (pkgName) {
545
- importedPackages.add(pkgName);
546
- }
547
- }
548
- } catch (error) {
549
- // Skip files with errors
550
- }
551
- });
552
-
553
- // Scan test files separately
554
- testFiles.forEach(filePath => {
555
- try {
556
- const content = fs.readFileSync(filePath, 'utf8');
557
- const importPattern = /from\s+['"]([^'"]+)['"]/g;
558
- let match;
559
- while ((match = importPattern.exec(content)) !== null) {
560
- const modulePath = match[1];
561
- const pkgName = extractPackageName(modulePath);
562
- if (pkgName && !importedPackages.has(pkgName)) {
563
- // Only add to test-only if not already used in runtime code
564
- testOnlyPackages.add(pkgName);
565
- }
566
- }
567
- const requirePattern = /require\(['"]([^'"]+)['"]\)/g;
568
- while ((match = requirePattern.exec(content)) !== null) {
569
- const modulePath = match[1];
570
- const pkgName = extractPackageName(modulePath);
571
- if (pkgName && !importedPackages.has(pkgName)) {
572
- testOnlyPackages.add(pkgName);
573
- }
574
- }
575
- } catch (error) {
576
- // Skip files with errors
577
- }
578
- });
579
-
580
- // Scan config files for imports
581
- configFiles.forEach(filePath => {
582
- try {
583
- const content = fs.readFileSync(filePath, 'utf8');
584
- const importPattern = /from\s+['"]([^'"]+)['"]/g;
585
- let match;
586
- while ((match = importPattern.exec(content)) !== null) {
587
- const modulePath = match[1];
588
- const pkgName = extractPackageName(modulePath);
589
- if (pkgName) {
590
- importedPackages.add(pkgName);
591
- }
592
- }
593
- const requirePattern = /require\(['"]([^'"]+)['"]\)/g;
594
- while ((match = requirePattern.exec(content)) !== null) {
595
- const modulePath = match[1];
596
- const pkgName = extractPackageName(modulePath);
597
- if (pkgName) {
598
- importedPackages.add(pkgName);
599
- }
600
- }
601
- } catch (error) {
602
- // Skip files with errors
603
- }
604
- });
605
-
606
- // Known build tools and dev dependencies that are commonly used but not directly imported
607
- const knownBuildTools = [
608
- 'vite',
609
- 'typescript',
610
- 'eslint',
611
- 'prettier',
612
- 'vitest',
613
- '@types',
614
- '@vitejs',
615
- '@testing-library',
616
- 'jsdom',
617
- 'globals',
618
- 'tailwindcss',
619
- '@tailwindcss',
620
- 'postcss',
621
- 'autoprefixer',
622
- 'babel',
623
- 'webpack',
624
- 'rollup',
625
- 'tsup',
626
- 'esbuild',
627
- 'lovable-tagger',
628
- 'babel-plugin-react-compiler',
629
- ];
630
-
631
- // Check for potentially unused dependencies
632
- // Skip this check if running on the pace-core repository itself
633
- // The library's dependencies are used in packages/core/src/, not in the root src/ (demo app)
634
- if (!isPaceCoreRepository) {
635
- Object.keys(allDeps).forEach(dep => {
636
- // Skip if it's a known build tool
637
- if (knownBuildTools.some(tool => dep.includes(tool) || dep.startsWith(tool))) {
638
- return;
639
- }
640
-
641
- // Skip if it's a peer dependency of pace-core
642
- if (paceCorePeerDeps[dep]) {
643
- return;
644
- }
645
-
646
- // Skip if it's pace-core itself
647
- if (dep === '@jmruthers/pace-core') {
648
- return;
649
- }
650
-
651
- // Skip if it's a dependency of pace-core (required by pace-core's bundled code)
652
- if (!isPaceCoreRepository) {
653
- const paceCoreDeps = getPaceCoreDeps(projectRoot);
654
- if (paceCoreDeps[dep]) {
655
- // This dependency is required by pace-core, so it's not unused
656
- return;
657
- }
658
- }
659
-
660
- // Check if package is imported in source files (runtime)
661
- if (importedPackages.has(dep)) {
662
- // Package is used in runtime code, so it's needed
663
- return;
664
- }
665
-
666
- // Check if it's used in package.json scripts
667
- if (checkPackageScripts(packageJson, dep)) {
668
- return;
669
- }
670
-
671
- // Check if it's used in config files
672
- if (checkConfigFiles(configFiles, dep)) {
673
- return;
674
- }
675
-
676
- // Check if it's used in CSS or other non-JS files
677
- if (checkNonJsFiles(projectRoot, dep)) {
678
- return;
679
- }
680
-
681
- // Check if it's used in test files
682
- const isUsedInTests = testOnlyPackages.has(dep) || isOnlyUsedInTests(projectRoot, dep, testFiles);
683
- const isOnlyInTests = isUsedInTests && !importedPackages.has(dep);
684
-
685
- // For dev dependencies, provide more context
686
- const isDevDep = dep in devDeps;
687
- const context = [];
688
-
689
- if (isDevDep) {
690
- context.push('This is a dev dependency and may be used by build tools or test frameworks');
691
- }
692
-
693
- // Check if it might be a transitive dependency needed by pace-core
694
- if (paceCoreVersion) {
695
- context.push('This may be required by @jmruthers/pace-core as a transitive dependency');
696
- }
697
-
698
- // Check if it's in vite.config.ts optimizeDeps (common for pace-core peer deps)
699
- const viteConfigPath = path.join(projectRoot, 'vite.config.ts');
700
- const viteConfigJsPath = path.join(projectRoot, 'vite.config.js');
701
- let isInViteConfig = false;
702
-
703
- for (const configPath of [viteConfigPath, viteConfigJsPath]) {
704
- if (fs.existsSync(configPath)) {
705
- try {
706
- const viteContent = fs.readFileSync(configPath, 'utf8');
707
- if (viteContent.includes(dep)) {
708
- isInViteConfig = true;
709
- break;
710
- }
711
- } catch (error) {
712
- // Skip if can't read
713
- }
714
- }
715
- }
716
-
717
- // Only warn if we're reasonably sure it's unused
718
- // For runtime dependencies, be more strict
719
- if (!isDevDep) {
720
- // If it's only used in tests, suggest moving to devDependencies
721
- if (isOnlyInTests) {
722
- warnings.push({
723
- type: 'test-only-runtime-dependency',
724
- file: 'package.json',
725
- message: `Runtime dependency '${dep}' is only used in test files`,
726
- recommendation: `Move '${dep}' from dependencies to devDependencies since it's only used in tests. This will reduce your production bundle size.`
727
- });
728
- return;
729
- }
730
-
731
- const recommendation = `Verify if '${dep}' is actually used. ${context.length > 0 ? context.join(' ') : ''}${isInViteConfig ? 'This dependency is referenced in vite.config.ts, which suggests it may be required by pace-core components. ' : ''}If it's a peer dependency of @jmruthers/pace-core (check pace-core's package.json), it's required even if not directly imported. If confirmed unused, remove it to reduce bundle size.`;
732
-
733
- warnings.push({
734
- type: 'unused-dependency',
735
- file: 'package.json',
736
- message: `Runtime dependency '${dep}' may be unused`,
737
- recommendation: recommendation
738
- });
739
- } else {
740
- // For dev dependencies, only suggest (lower severity)
741
- suggestions.push({
742
- type: 'potentially-unused-dev-dependency',
743
- file: 'package.json',
744
- message: `Dev dependency '${dep}' may be unused`,
745
- recommendation: `Check if '${dep}' is used in build configs, test files, or by pace-core. ${context.join(' ')}${isInViteConfig ? 'This dependency is referenced in vite.config.ts. ' : ''}If confirmed unused, you can remove it.`
746
- });
747
- }
748
- });
749
- }
750
-
751
- // Check for missing peer dependencies from pace-core
752
- Object.keys(paceCorePeerDeps).forEach(peerDep => {
753
- if (!allDeps[peerDep]) {
754
- warnings.push({
755
- type: 'missing-peer-dependency',
756
- file: 'package.json',
757
- message: `Peer dependency '${peerDep}' is missing but required by @jmruthers/pace-core`,
758
- recommendation: `Install '${peerDep}' as it's required by pace-core. Run: npm install ${peerDep}`
759
- });
760
- }
761
- });
762
-
763
- // Check for version mismatches between react and react-dom
764
- // (The required version check is done earlier in the function)
765
- const reactVersionForMismatch = packageJson.dependencies?.react || packageJson.devDependencies?.react;
766
- const reactDomVersionForMismatch = packageJson.dependencies?.['react-dom'] || packageJson.devDependencies?.['react-dom'];
767
-
768
- if (reactVersionForMismatch && reactDomVersionForMismatch && reactVersionForMismatch !== reactDomVersionForMismatch) {
769
- issues.push({
770
- type: 'version-mismatch',
771
- file: 'package.json',
772
- message: `React version mismatch: react@${reactVersionForMismatch} vs react-dom@${reactDomVersionForMismatch}`,
773
- recommendation: 'React and react-dom versions must match exactly'
774
- });
775
- }
776
-
777
- // ============================================
778
- // NEW CHECKS: Future-proofing dependency management
779
- // ============================================
780
-
781
- // 1. Check for redundant dependencies in consuming apps
782
- // (packages already provided by pace-core)
783
- if (!isPaceCoreRepository) {
784
- const paceCoreDeps = getPaceCoreDeps(projectRoot);
785
- const paceCorePeerDeps = getPaceCorePeerDeps(projectRoot);
786
-
787
- // Packages that pace-core provides as dependencies (should not be duplicated)
788
- const redundantDeps = [];
789
- Object.keys(packageJson.dependencies || {}).forEach(dep => {
790
- // Skip pace-core itself
791
- if (dep === '@jmruthers/pace-core') {
792
- return;
793
- }
794
-
795
- // Check if it's provided by pace-core as a dependency
796
- if (paceCoreDeps[dep]) {
797
- redundantDeps.push({
798
- dep,
799
- reason: 'provided by pace-core as a dependency',
800
- paceCoreVersion: paceCoreDeps[dep]
801
- });
802
- }
803
- });
804
-
805
- redundantDeps.forEach(({ dep, reason, paceCoreVersion }) => {
806
- warnings.push({
807
- type: 'redundant-dependency',
808
- file: 'package.json',
809
- message: `Dependency '${dep}' is redundant - ${reason}`,
810
- recommendation: `Remove '${dep}' from dependencies. It's already provided by @jmruthers/pace-core@${paceCoreVersion}. Exception: If you need a different version or direct control, keep it but document why.`
811
- });
812
- });
813
- }
814
-
815
- // 2. Check for missing dependencies (used but not declared)
816
- if (!isPaceCoreRepository) {
817
- // Check for dynamic imports that might need to be declared
818
- const optionalDeps = ['recharts', 'papaparse', 'lodash.debounce', 'lodash.throttle'];
819
- const dynamicImportPattern = /import\(['"]([^'"]+)['"]\)/g;
820
-
821
- sourceFiles.forEach(filePath => {
822
- try {
823
- const content = fs.readFileSync(filePath, 'utf8');
824
- let match;
825
- while ((match = dynamicImportPattern.exec(content)) !== null) {
826
- const modulePath = match[1];
827
- const pkgName = extractPackageName(modulePath);
828
- if (pkgName && optionalDeps.includes(pkgName)) {
829
- // Check if it's declared
830
- if (!allDeps[pkgName]) {
831
- suggestions.push({
832
- type: 'missing-optional-dependency',
833
- file: path.relative(projectRoot, filePath),
834
- message: `Optional dependency '${pkgName}' is dynamically imported but not declared`,
835
- recommendation: `Add '${pkgName}' to dependencies if you use this feature, or ensure it's marked as external in your build config`
836
- });
837
- }
838
- }
839
- }
840
- } catch (error) {
841
- // Skip files with errors
842
- }
843
- });
844
- }
845
-
846
- // 3. Check for misclassified dependencies
847
- // Runtime deps in devDependencies, peer deps in dependencies, etc.
848
- if (isPaceCoreRepository) {
849
- // Check pace-core's own package.json
850
- const packagesCorePath = path.join(projectRoot, 'packages', 'core', 'package.json');
851
- if (fs.existsSync(packagesCorePath)) {
852
- const corePkg = JSON.parse(fs.readFileSync(packagesCorePath, 'utf8'));
853
- const coreDeps = corePkg.dependencies || {};
854
- const coreDevDeps = corePkg.devDependencies || {};
855
- const corePeerDeps = corePkg.peerDependencies || {};
856
-
857
- // Check if peer dependencies are incorrectly in dependencies
858
- Object.keys(coreDeps).forEach(dep => {
859
- if (corePeerDeps[dep]) {
860
- issues.push({
861
- type: 'misclassified-dependency',
862
- file: 'packages/core/package.json',
863
- message: `'${dep}' is declared as both dependency and peerDependency`,
864
- recommendation: `Remove '${dep}' from dependencies. Peer dependencies should only be in peerDependencies.`
865
- });
866
- }
867
- });
868
-
869
- // Check if runtime dependencies are in devDependencies
870
- // This is harder to detect automatically, but we can check for common patterns
871
- const runtimeDepsInDev = ['@hookform/resolvers', '@supabase/supabase-js', '@tanstack/react-query'];
872
- runtimeDepsInDev.forEach(dep => {
873
- if (coreDevDeps[dep] && !coreDeps[dep]) {
874
- // Check if it's used in source code
875
- const coreSrcPath = path.join(projectRoot, 'packages', 'core', 'src');
876
- if (fs.existsSync(coreSrcPath)) {
877
- const coreSourceFiles = findSourceFiles(coreSrcPath);
878
- let isUsed = false;
879
- coreSourceFiles.forEach(filePath => {
880
- try {
881
- const content = fs.readFileSync(filePath, 'utf8');
882
- if (content.includes(dep) || content.includes(extractPackageName(dep))) {
883
- isUsed = true;
884
- }
885
- } catch (error) {
886
- // Skip
887
- }
888
- });
889
-
890
- if (isUsed) {
891
- warnings.push({
892
- type: 'misclassified-dependency',
893
- file: 'packages/core/package.json',
894
- message: `'${dep}' is in devDependencies but appears to be used in source code`,
895
- recommendation: `Move '${dep}' from devDependencies to dependencies if it's used at runtime`
896
- });
897
- }
898
- }
899
- }
900
- });
901
- }
902
- }
903
-
904
- // 4. Check for version mismatches across workspace (monorepo)
905
- if (isPaceCoreRepository) {
906
- const rootPkg = getWorkspaceRootPackageJson(projectRoot);
907
- const corePkg = getPaceCorePackageJson(projectRoot);
908
-
909
- if (rootPkg && corePkg) {
910
- const rootDeps = { ...rootPkg.dependencies, ...rootPkg.devDependencies };
911
- const coreDeps = { ...corePkg.dependencies, ...corePkg.devDependencies };
912
-
913
- // Check for version mismatches in common dependencies
914
- const commonDeps = Object.keys(rootDeps).filter(dep => coreDeps[dep]);
915
- commonDeps.forEach(dep => {
916
- const rootVersion = rootDeps[dep];
917
- const coreVersion = coreDeps[dep];
918
-
919
- // Normalize versions for comparison (remove ^, ~, etc.)
920
- const normalizeVersion = (v) => v.replace(/^[\^~]/, '');
921
- const rootNormalized = normalizeVersion(rootVersion);
922
- const coreNormalized = normalizeVersion(coreVersion);
923
-
924
- if (rootNormalized !== coreNormalized) {
925
- warnings.push({
926
- type: 'workspace-version-mismatch',
927
- file: 'package.json',
928
- message: `Version mismatch for '${dep}': root@${rootVersion} vs packages/core@${coreVersion}`,
929
- recommendation: `Align versions across workspace. Consider using the same version in both root and packages/core package.json files.`
930
- });
931
- }
932
- });
933
- }
934
- }
935
-
936
- // 5. Check for missing peer dependencies in consuming apps
937
- if (!isPaceCoreRepository) {
938
- const paceCorePkg = getPaceCorePackageJson(projectRoot);
939
- if (paceCorePkg) {
940
- const peerDeps = paceCorePkg.peerDependencies || {};
941
- const allDeps = { ...packageJson.dependencies, ...packageJson.devDependencies };
942
-
943
- Object.keys(peerDeps).forEach(peerDep => {
944
- if (!allDeps[peerDep]) {
945
- issues.push({
946
- type: 'missing-peer-dependency',
947
- file: 'package.json',
948
- message: `Missing peer dependency '${peerDep}' required by @jmruthers/pace-core`,
949
- recommendation: `Install '${peerDep}@${peerDeps[peerDep]}' as a dependency. Run: npm install ${peerDep}@${peerDeps[peerDep]}`
950
- });
951
- }
952
- });
953
- }
954
- }
955
-
956
- // 6. Check for optional dependencies handling in vite.config
957
- if (!isPaceCoreRepository) {
958
- const viteConfigPath = path.join(projectRoot, 'vite.config.ts');
959
- const viteConfigJsPath = path.join(projectRoot, 'vite.config.js');
960
- const optionalDeps = ['recharts', 'papaparse'];
961
-
962
- for (const configPath of [viteConfigPath, viteConfigJsPath]) {
963
- if (fs.existsSync(configPath)) {
964
- try {
965
- const viteContent = fs.readFileSync(configPath, 'utf8');
966
- optionalDeps.forEach(dep => {
967
- // Check if it's marked as external
968
- const isExternal = viteContent.includes(`external`) &&
969
- (viteContent.includes(`'${dep}'`) || viteContent.includes(`"${dep}"`));
970
-
971
- // Check if it's in dependencies
972
- const isInDeps = allDeps[dep];
973
-
974
- if (!isExternal && !isInDeps) {
975
- suggestions.push({
976
- type: 'optional-dependency-config',
977
- file: path.relative(projectRoot, configPath),
978
- message: `Optional dependency '${dep}' should be handled in build config`,
979
- recommendation: `Add '${dep}' to build.rollupOptions.external if you want it resolved at runtime, or install it as a dependency if you want it bundled`
980
- });
981
- }
982
- });
983
- } catch (error) {
984
- // Skip if can't read
985
- }
986
- }
987
- }
988
- }
989
-
990
- return { issues, warnings, suggestions };
991
- }
992
- };
993
-
994
- module.exports = dependenciesCheck;