@jmruthers/pace-core 0.6.1 → 0.6.3

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (549) hide show
  1. package/CHANGELOG.md +88 -10
  2. package/cursor-rules/00-pace-core-compliance.mdc +46 -87
  3. package/cursor-rules/01-standards-compliance.mdc +16 -47
  4. package/cursor-rules/02-project-structure.mdc +4 -4
  5. package/cursor-rules/03-solid-principles.mdc +45 -164
  6. package/cursor-rules/04-testing-standards.mdc +22 -69
  7. package/cursor-rules/05-bug-reports-and-features.mdc +2 -2
  8. package/cursor-rules/06-code-quality.mdc +42 -125
  9. package/cursor-rules/07-tech-stack-compliance.mdc +33 -128
  10. package/cursor-rules/08-markup-quality.mdc +452 -0
  11. package/cursor-rules/CHANGELOG.md +18 -0
  12. package/cursor-rules/README.md +2 -1
  13. package/dist/{AuthService-DjnJHDtC.d.ts → AuthService-Cb34EQs3.d.ts} +63 -1
  14. package/dist/{DataTable-CH1U5Tpy.d.ts → DataTable-BMRU8a1j.d.ts} +33 -1
  15. package/dist/{DataTable-DQ7RSOHE.js → DataTable-THFPBKTP.js} +12 -10
  16. package/dist/{PublicPageProvider-ce4xlHYA.d.ts → PublicPageProvider-DEMpysFR.d.ts} +394 -171
  17. package/dist/{UnifiedAuthProvider-185Ih4dj.d.ts → UnifiedAuthProvider-CKvHP1MK.d.ts} +30 -8
  18. package/dist/{UnifiedAuthProvider-ATAP5UTR.js → UnifiedAuthProvider-KAGUYQ4J.js} +5 -4
  19. package/dist/{api-N774RPUA.js → api-IAGWF3ZG.js} +10 -10
  20. package/dist/{audit-B5P6FFIR.js → audit-V53FV5AG.js} +2 -2
  21. package/dist/{chunk-JBKQ3SAO.js → chunk-2T2IG7T7.js} +107 -57
  22. package/dist/chunk-2T2IG7T7.js.map +1 -0
  23. package/dist/{chunk-3QRJFVBR.js → chunk-6SOIHG6Z.js} +1 -1
  24. package/dist/chunk-6SOIHG6Z.js.map +1 -0
  25. package/dist/{chunk-3XTALGJF.js → chunk-6Z7LTB3D.js} +69 -240
  26. package/dist/chunk-6Z7LTB3D.js.map +1 -0
  27. package/dist/{chunk-4ZC4GX36.js → chunk-CNCQDFLN.js} +199 -46
  28. package/dist/chunk-CNCQDFLN.js.map +1 -0
  29. package/dist/chunk-DGUM43GV.js +11 -0
  30. package/dist/{chunk-BYFSK72L.js → chunk-DWUBLJJM.js} +361 -187
  31. package/dist/chunk-DWUBLJJM.js.map +1 -0
  32. package/dist/{chunk-LXQLPRQ2.js → chunk-FFQEQTNW.js} +6 -8
  33. package/dist/chunk-FFQEQTNW.js.map +1 -0
  34. package/dist/chunk-FMUCXFII.js +76 -0
  35. package/dist/chunk-FMUCXFII.js.map +1 -0
  36. package/dist/{chunk-4N5C5XZU.js → chunk-HFZBI76P.js} +4 -4
  37. package/dist/chunk-HFZBI76P.js.map +1 -0
  38. package/dist/{chunk-SQGMNID3.js → chunk-L4OXEN46.js} +4 -5
  39. package/dist/chunk-L4OXEN46.js.map +1 -0
  40. package/dist/{chunk-R77UEZ4E.js → chunk-M43Y4SSO.js} +1 -1
  41. package/dist/chunk-M43Y4SSO.js.map +1 -0
  42. package/dist/{chunk-I7PSE6JW.js → chunk-M7MPQISP.js} +3 -76
  43. package/dist/chunk-M7MPQISP.js.map +1 -0
  44. package/dist/chunk-PQBSKX33.js +7793 -0
  45. package/dist/chunk-PQBSKX33.js.map +1 -0
  46. package/dist/chunk-QRPVRXYT.js +226 -0
  47. package/dist/chunk-QRPVRXYT.js.map +1 -0
  48. package/dist/{chunk-KNC55RTG.js → chunk-RWEBCB47.js} +194 -416
  49. package/dist/chunk-RWEBCB47.js.map +1 -0
  50. package/dist/{chunk-XM25TVIE.js → chunk-YDQHOZNA.js} +843 -388
  51. package/dist/chunk-YDQHOZNA.js.map +1 -0
  52. package/dist/{chunk-GLK6VM3F.js → chunk-ZNIWI3UC.js} +739 -737
  53. package/dist/chunk-ZNIWI3UC.js.map +1 -0
  54. package/dist/components.d.ts +5 -5
  55. package/dist/components.js +18 -16
  56. package/dist/components.js.map +1 -1
  57. package/dist/contextValidator-3JNZKUTX.js +9 -0
  58. package/dist/contextValidator-3JNZKUTX.js.map +1 -0
  59. package/dist/eslint-rules/pace-core-compliance.cjs +106 -0
  60. package/dist/{functions-D_kgHktt.d.ts → functions-DHebl8-F.d.ts} +1 -1
  61. package/dist/hooks.d.ts +55 -122
  62. package/dist/hooks.js +10 -13
  63. package/dist/hooks.js.map +1 -1
  64. package/dist/index.d.ts +60 -13
  65. package/dist/index.js +30 -25
  66. package/dist/index.js.map +1 -1
  67. package/dist/providers.d.ts +21 -3
  68. package/dist/providers.js +4 -3
  69. package/dist/rbac/index.d.ts +210 -139
  70. package/dist/rbac/index.js +17 -13
  71. package/dist/styles/index.js +1 -1
  72. package/dist/theming/runtime.d.ts +1 -13
  73. package/dist/theming/runtime.js +2 -2
  74. package/dist/{timezone-_pgH8qrY.d.ts → timezone-CHhWg6b4.d.ts} +3 -10
  75. package/dist/{types-UU913iLA.d.ts → types-BeoeWV5I.d.ts} +8 -0
  76. package/dist/{types-CEpcvwwF.d.ts → types-CkbwOr4Y.d.ts} +6 -0
  77. package/dist/types.d.ts +2 -2
  78. package/dist/types.js +1 -1
  79. package/dist/{usePublicRouteParams-BJAlWfuJ.d.ts → usePublicRouteParams-i3qtoBgg.d.ts} +38 -17
  80. package/dist/utils.d.ts +4 -5
  81. package/dist/utils.js +17 -19
  82. package/dist/utils.js.map +1 -1
  83. package/docs/api/README.md +21 -17
  84. package/docs/api/modules.md +4191 -2967
  85. package/docs/architecture/database-schema-requirements.md +161 -0
  86. package/docs/components/context-selector.md +126 -0
  87. package/docs/core-concepts/rbac-system.md +3 -3
  88. package/docs/documentation-index.md +2 -4
  89. package/docs/getting-started/cursor-rules.md +2 -1
  90. package/docs/migration/DOCUMENTATION_STRUCTURE.md +441 -0
  91. package/docs/migration/MIGRATION_GUIDE.md +2 -24
  92. package/docs/migration/RBAC_SCOPE_MIGRATION.md +385 -0
  93. package/docs/migration/README.md +52 -6
  94. package/docs/migration/V0.5.190_TO_V0.6.1_MIGRATION.md +1153 -0
  95. package/docs/migration/database-changes-december-2025.md +3 -3
  96. package/docs/pace-mint-fix-auto-selection.md +218 -0
  97. package/docs/pace-mint-rbac-setup.md +391 -0
  98. package/docs/rbac/event-based-apps.md +1 -1
  99. package/docs/rbac/getting-started.md +1 -1
  100. package/docs/rbac/quick-start.md +1 -1
  101. package/docs/rbac/secure-client-protection.md +330 -0
  102. package/docs/standards/README.md +1 -0
  103. package/package.json +4 -3
  104. package/scripts/audit/core/checks/accessibility.cjs +197 -0
  105. package/scripts/audit/core/checks/api-usage.cjs +191 -0
  106. package/scripts/audit/core/checks/bundle.cjs +142 -0
  107. package/scripts/{check-pace-core-compliance.cjs → audit/core/checks/compliance.cjs} +784 -685
  108. package/scripts/audit/core/checks/config.cjs +54 -0
  109. package/scripts/audit/core/checks/coverage.cjs +84 -0
  110. package/scripts/audit/core/checks/dependencies.cjs +985 -0
  111. package/scripts/audit/core/checks/documentation.cjs +268 -0
  112. package/scripts/audit/core/checks/environment.cjs +116 -0
  113. package/scripts/audit/core/checks/error-handling.cjs +340 -0
  114. package/scripts/audit/core/checks/forms.cjs +172 -0
  115. package/scripts/audit/core/checks/heuristics.cjs +68 -0
  116. package/scripts/audit/core/checks/hooks.cjs +334 -0
  117. package/scripts/audit/core/checks/imports.cjs +244 -0
  118. package/scripts/audit/core/checks/performance.cjs +325 -0
  119. package/scripts/audit/core/checks/routes.cjs +117 -0
  120. package/scripts/audit/core/checks/state.cjs +130 -0
  121. package/scripts/audit/core/checks/structure.cjs +65 -0
  122. package/scripts/audit/core/checks/style.cjs +584 -0
  123. package/scripts/audit/core/checks/testing.cjs +122 -0
  124. package/scripts/audit/core/checks/typescript.cjs +61 -0
  125. package/scripts/audit/core/scanner.cjs +199 -0
  126. package/scripts/audit/core/utils.cjs +137 -0
  127. package/scripts/audit/index.cjs +223 -0
  128. package/scripts/audit/reporters/console.cjs +151 -0
  129. package/scripts/audit/reporters/json.cjs +54 -0
  130. package/scripts/audit/reporters/markdown.cjs +124 -0
  131. package/scripts/audit-consuming-app.cjs +61 -936
  132. package/scripts/build-docs/build-decision.js +240 -0
  133. package/scripts/build-docs/cache-utils.js +105 -0
  134. package/scripts/build-docs/content-normalization.js +150 -0
  135. package/scripts/build-docs/file-utils.js +105 -0
  136. package/scripts/build-docs/git-utils.js +86 -0
  137. package/scripts/build-docs/hash-utils.js +116 -0
  138. package/scripts/build-docs/typedoc-runner.js +220 -0
  139. package/scripts/build-docs-incremental.js +77 -913
  140. package/scripts/utils/command-runner.js +16 -11
  141. package/scripts/validate-formats.js +61 -56
  142. package/scripts/validate-master.js +74 -69
  143. package/scripts/validate-pre-publish.js +70 -65
  144. package/src/__tests__/hooks/usePermissions.test.ts +2 -2
  145. package/src/components/Alert/Alert.test.tsx +12 -18
  146. package/src/components/Alert/Alert.tsx +5 -7
  147. package/src/components/Avatar/Avatar.test.tsx +4 -4
  148. package/src/components/Badge/Badge.tsx +14 -0
  149. package/src/components/Button/Button.tsx +22 -0
  150. package/src/components/Calendar/Calendar.tsx +8 -2
  151. package/src/components/Card/Card.tsx +4 -0
  152. package/src/components/Checkbox/Checkbox.test.tsx +12 -12
  153. package/src/components/Checkbox/Checkbox.tsx +2 -2
  154. package/src/components/ContextSelector/ContextSelector.tsx +384 -0
  155. package/src/components/ContextSelector/index.ts +3 -0
  156. package/src/components/DataTable/DataTable.tsx +38 -4
  157. package/src/components/DataTable/__tests__/DataTableCore.test-setup.ts +5 -6
  158. package/src/components/DataTable/__tests__/pagination.modes.test.tsx +18 -4
  159. package/src/components/DataTable/__tests__/test-utils/sharedTestUtils.tsx +2 -3
  160. package/src/components/DataTable/components/AccessDeniedPage.tsx +16 -25
  161. package/src/components/DataTable/components/ActionButtons.tsx +10 -7
  162. package/src/components/DataTable/components/BulkOperationsDropdown.tsx +1 -1
  163. package/src/components/DataTable/components/ColumnFilter.tsx +10 -0
  164. package/src/components/DataTable/components/ColumnVisibilityDropdown.tsx +12 -0
  165. package/src/components/DataTable/components/DataTableBody.tsx +8 -0
  166. package/src/components/DataTable/components/DataTableCore.tsx +196 -554
  167. package/src/components/DataTable/components/DataTableErrorBoundary.tsx +11 -0
  168. package/src/components/DataTable/components/DataTableLayout.tsx +559 -0
  169. package/src/components/DataTable/components/DataTableModals.tsx +8 -0
  170. package/src/components/DataTable/components/DataTableToolbar.tsx +8 -0
  171. package/src/components/DataTable/components/DraggableColumnHeader.tsx +12 -0
  172. package/src/components/DataTable/components/EditFields.tsx +307 -0
  173. package/src/components/DataTable/components/EditableRow.tsx +8 -0
  174. package/src/components/DataTable/components/EmptyState.tsx +10 -0
  175. package/src/components/DataTable/components/FilterRow.tsx +12 -0
  176. package/src/components/DataTable/components/GroupHeader.tsx +12 -0
  177. package/src/components/DataTable/components/GroupingDropdown.tsx +12 -0
  178. package/src/components/DataTable/components/ImportModal.tsx +7 -0
  179. package/src/components/DataTable/components/LoadingState.tsx +6 -0
  180. package/src/components/DataTable/components/PaginationControls.tsx +16 -1
  181. package/src/components/DataTable/components/RowComponent.tsx +391 -0
  182. package/src/components/DataTable/components/UnifiedTableBody.tsx +63 -851
  183. package/src/components/DataTable/components/VirtualizedDataTable.tsx +16 -4
  184. package/src/components/DataTable/components/__tests__/AccessDeniedPage.test.tsx +4 -2
  185. package/src/components/DataTable/components/cellValueUtils.ts +40 -0
  186. package/src/components/DataTable/components/hooks/useImportModalFocus.ts +53 -0
  187. package/src/components/DataTable/components/hooks/usePermissionTracking.ts +126 -0
  188. package/src/components/DataTable/context/DataTableContext.tsx +50 -0
  189. package/src/components/DataTable/core/ColumnFactory.ts +31 -0
  190. package/src/components/DataTable/core/DataTableContext.tsx +32 -1
  191. package/src/components/DataTable/hooks/useColumnOrderPersistence.ts +10 -0
  192. package/src/components/DataTable/hooks/useColumnReordering.ts +12 -0
  193. package/src/components/DataTable/hooks/useColumnVisibilityPersistence.ts +10 -0
  194. package/src/components/DataTable/hooks/useDataTableDataPipeline.ts +16 -0
  195. package/src/components/DataTable/hooks/useDataTablePermissions.ts +127 -33
  196. package/src/components/DataTable/hooks/useDataTableState.ts +35 -1
  197. package/src/components/DataTable/hooks/useEffectiveColumnOrder.ts +12 -0
  198. package/src/components/DataTable/hooks/useServerSideDataEffect.ts +11 -0
  199. package/src/components/DataTable/hooks/useTableColumns.ts +8 -0
  200. package/src/components/DataTable/hooks/useTableHandlers.ts +14 -0
  201. package/src/components/DataTable/styles.ts +6 -6
  202. package/src/components/DataTable/types.ts +6 -10
  203. package/src/components/DataTable/utils/a11yUtils.ts +7 -0
  204. package/src/components/DataTable/utils/debugTools.ts +18 -113
  205. package/src/components/DataTable/utils/errorHandling.ts +12 -0
  206. package/src/components/DataTable/utils/exportUtils.ts +9 -0
  207. package/src/components/DataTable/utils/flexibleImport.ts +12 -48
  208. package/src/components/DataTable/utils/paginationUtils.ts +8 -0
  209. package/src/components/DataTable/utils/performanceUtils.ts +5 -1
  210. package/src/components/Dialog/Dialog.tsx +31 -3
  211. package/src/components/ErrorBoundary/ErrorBoundary.test.tsx +180 -1
  212. package/src/components/ErrorBoundary/ErrorBoundary.tsx +45 -5
  213. package/src/components/ErrorBoundary/ErrorBoundaryContext.tsx +129 -0
  214. package/src/components/ErrorBoundary/index.ts +27 -2
  215. package/src/components/FileDisplay/FileDisplay.tsx +74 -28
  216. package/src/components/FileUpload/FileUpload.tsx +22 -2
  217. package/src/components/Footer/Footer.test.tsx +16 -16
  218. package/src/components/Footer/Footer.tsx +14 -11
  219. package/src/components/Form/Form.tsx +1 -0
  220. package/src/components/Header/Header.test.tsx +43 -73
  221. package/src/components/Header/Header.tsx +59 -49
  222. package/src/components/Input/Input.test.tsx +2 -2
  223. package/src/components/Input/Input.tsx +8 -4
  224. package/src/components/LoadingSpinner/LoadingSpinner.test.tsx +4 -4
  225. package/src/components/LoginForm/LoginForm.tsx +4 -0
  226. package/src/components/NavigationMenu/NavigationMenu.tsx +14 -513
  227. package/src/components/NavigationMenu/types.ts +56 -0
  228. package/src/components/NavigationMenu/useNavigationFiltering.ts +390 -0
  229. package/src/components/PaceAppLayout/PaceAppLayout.integration.test.tsx +10 -19
  230. package/src/components/PaceAppLayout/PaceAppLayout.performance.test.tsx +2 -2
  231. package/src/components/PaceAppLayout/PaceAppLayout.security.test.tsx +5 -5
  232. package/src/components/PaceAppLayout/PaceAppLayout.test.tsx +13 -11
  233. package/src/components/PaceAppLayout/PaceAppLayout.tsx +167 -44
  234. package/src/components/PaceAppLayout/README.md +14 -17
  235. package/src/components/PaceAppLayout/test-setup.tsx +3 -4
  236. package/src/components/PaceLoginPage/PaceLoginPage.tsx +3 -0
  237. package/src/components/PasswordChange/PasswordChangeForm.tsx +9 -0
  238. package/src/components/ProtectedRoute/ProtectedRoute.tsx +3 -9
  239. package/src/components/PublicLayout/PublicPageLayout.tsx +2 -5
  240. package/src/components/PublicLayout/PublicPageProvider.tsx +4 -0
  241. package/src/components/Select/Select.tsx +80 -434
  242. package/src/components/Select/context.ts +23 -0
  243. package/src/components/Select/hooks/useSelectEvents.ts +87 -0
  244. package/src/components/Select/hooks/useSelectSearch.ts +91 -0
  245. package/src/components/Select/hooks/useSelectState.ts +104 -0
  246. package/src/components/Select/index.ts +9 -1
  247. package/src/components/Select/types.ts +123 -0
  248. package/src/components/Select/utils/text.ts +26 -0
  249. package/src/components/SessionRestorationLoader/SessionRestorationLoader.tsx +4 -5
  250. package/src/components/Switch/Switch.tsx +4 -4
  251. package/src/components/Tabs/Tabs.tsx +1 -1
  252. package/src/components/Toast/Toast.tsx +4 -0
  253. package/src/components/Tooltip/Tooltip.tsx +2 -2
  254. package/src/components/UserMenu/UserMenu.test.tsx +24 -11
  255. package/src/components/UserMenu/UserMenu.tsx +21 -18
  256. package/src/components/index.ts +7 -7
  257. package/src/eslint-rules/pace-core-compliance.cjs +106 -0
  258. package/src/hooks/__tests__/index.unit.test.ts +2 -5
  259. package/src/hooks/__tests__/useAppConfig.unit.test.ts +4 -98
  260. package/src/hooks/index.ts +1 -2
  261. package/src/hooks/public/usePublicEvent.ts +4 -0
  262. package/src/hooks/public/usePublicEventLogo.ts +4 -0
  263. package/src/hooks/public/usePublicFileDisplay.ts +4 -0
  264. package/src/hooks/public/usePublicRouteParams.ts +4 -0
  265. package/src/hooks/services/useAuth.ts +32 -0
  266. package/src/hooks/services/useCurrentEvent.ts +6 -0
  267. package/src/hooks/services/useCurrentOrganisation.ts +6 -0
  268. package/src/hooks/useAppConfig.ts +15 -30
  269. package/src/hooks/useDebounce.ts +9 -0
  270. package/src/hooks/useEventTheme.ts +6 -0
  271. package/src/hooks/useFileDisplay.ts +81 -50
  272. package/src/hooks/useFileReference.ts +25 -7
  273. package/src/hooks/useFileUrl.ts +11 -1
  274. package/src/hooks/useFocusManagement.ts +14 -0
  275. package/src/hooks/useFocusTrap.ts +3 -0
  276. package/src/hooks/useInactivityTracker.ts +3 -0
  277. package/src/hooks/useKeyboardShortcuts.ts +4 -0
  278. package/src/hooks/useOrganisationPermissions.ts +4 -0
  279. package/src/hooks/useOrganisationSecurity.ts +4 -0
  280. package/src/hooks/usePerformanceMonitor.ts +4 -0
  281. package/src/hooks/usePermissionCache.ts +7 -0
  282. package/src/hooks/useQueryCache.ts +12 -1
  283. package/src/hooks/useSessionRestoration.ts +4 -0
  284. package/src/hooks/useStorage.ts +4 -0
  285. package/src/hooks/useToast.ts +1 -1
  286. package/src/index.ts +6 -6
  287. package/src/providers/__tests__/OrganisationProvider.test.tsx +92 -70
  288. package/src/providers/services/AuthServiceProvider.tsx +35 -7
  289. package/src/providers/services/EventServiceProvider.tsx +51 -5
  290. package/src/providers/services/InactivityServiceProvider.tsx +18 -0
  291. package/src/providers/services/OrganisationServiceProvider.tsx +18 -0
  292. package/src/providers/services/UnifiedAuthProvider.tsx +126 -134
  293. package/src/providers/services/__tests__/AuthServiceProvider.integration.test.tsx +29 -13
  294. package/src/rbac/README.md +1 -1
  295. package/src/rbac/__tests__/adapters.comprehensive.test.tsx +1 -1
  296. package/src/rbac/__tests__/scenarios.user-role.test.tsx +4 -5
  297. package/src/rbac/adapters.tsx +12 -3
  298. package/src/rbac/api.test.ts +59 -51
  299. package/src/rbac/api.ts +246 -167
  300. package/src/rbac/components/NavigationProvider.tsx +4 -1
  301. package/src/rbac/components/PagePermissionGuard.tsx +185 -17
  302. package/src/rbac/components/RoleBasedRouter.tsx +5 -1
  303. package/src/rbac/components/SecureDataProvider.test.tsx +84 -49
  304. package/src/rbac/components/SecureDataProvider.tsx +20 -5
  305. package/src/rbac/components/__tests__/PagePermissionGuard.race-condition.test.tsx +24 -14
  306. package/src/rbac/components/__tests__/PagePermissionGuard.test.tsx +7 -0
  307. package/src/rbac/components/__tests__/PagePermissionGuard.verification.test.tsx +14 -6
  308. package/src/rbac/components/__tests__/RoleBasedRouter.test.tsx +15 -4
  309. package/src/rbac/components/__tests__/SecureDataProvider.fixed.test.tsx +148 -24
  310. package/src/rbac/components/__tests__/SecureDataProvider.test.tsx +81 -15
  311. package/src/rbac/engine.ts +38 -14
  312. package/src/rbac/hooks/__tests__/useSecureSupabase.test.ts +32 -21
  313. package/src/rbac/hooks/permissions/index.ts +7 -0
  314. package/src/rbac/hooks/permissions/useAccessLevel.ts +105 -0
  315. package/src/rbac/hooks/permissions/useCachedPermissions.ts +79 -0
  316. package/src/rbac/hooks/permissions/useCan.ts +377 -0
  317. package/src/rbac/hooks/permissions/useHasAllPermissions.ts +90 -0
  318. package/src/rbac/hooks/permissions/useHasAnyPermission.ts +90 -0
  319. package/src/rbac/hooks/permissions/useMultiplePermissions.ts +93 -0
  320. package/src/rbac/hooks/permissions/usePermissions.ts +253 -0
  321. package/src/rbac/hooks/useCan.test.ts +64 -66
  322. package/src/rbac/hooks/usePermissions.ts +14 -995
  323. package/src/rbac/hooks/useRBAC.test.ts +1 -5
  324. package/src/rbac/hooks/useRBAC.ts +36 -37
  325. package/src/rbac/hooks/useResolvedScope.test.ts +120 -35
  326. package/src/rbac/hooks/useResolvedScope.ts +35 -40
  327. package/src/rbac/hooks/useResourcePermissions.test.ts +54 -18
  328. package/src/rbac/hooks/useResourcePermissions.ts +14 -4
  329. package/src/rbac/hooks/useSecureSupabase.ts +27 -7
  330. package/src/rbac/index.ts +7 -0
  331. package/src/rbac/permissions.ts +0 -30
  332. package/src/rbac/secureClient.test.ts +22 -18
  333. package/src/rbac/secureClient.ts +294 -68
  334. package/src/rbac/security.ts +0 -17
  335. package/src/rbac/types.ts +9 -0
  336. package/src/rbac/utils/__tests__/contextValidator.test.ts +64 -86
  337. package/src/rbac/utils/clientSecurity.ts +93 -0
  338. package/src/rbac/utils/contextValidator.ts +77 -168
  339. package/src/services/AuthService.ts +39 -7
  340. package/src/services/EventService.ts +186 -54
  341. package/src/services/OrganisationService.ts +81 -14
  342. package/src/services/__tests__/EventService.test.ts +1 -2
  343. package/src/services/base/BaseService.ts +3 -0
  344. package/src/theming/__tests__/parseEventColours.test.ts +6 -9
  345. package/src/theming/parseEventColours.ts +5 -19
  346. package/src/types/vitest-globals.d.ts +51 -26
  347. package/src/utils/__mocks__/supabaseMock.ts +1 -3
  348. package/src/utils/__tests__/formatting.unit.test.ts +4 -4
  349. package/src/utils/__tests__/index.unit.test.ts +2 -2
  350. package/src/utils/audit/audit.ts +0 -3
  351. package/src/utils/core/cn.ts +1 -1
  352. package/src/utils/dynamic/dynamicUtils.ts +7 -4
  353. package/src/utils/file-reference/index.ts +53 -1
  354. package/src/utils/formatting/formatting.ts +8 -18
  355. package/src/utils/index.ts +0 -1
  356. package/dist/chunk-3QRJFVBR.js.map +0 -1
  357. package/dist/chunk-3XTALGJF.js.map +0 -1
  358. package/dist/chunk-4N5C5XZU.js.map +0 -1
  359. package/dist/chunk-4ZC4GX36.js.map +0 -1
  360. package/dist/chunk-7D4SUZUM.js +0 -38
  361. package/dist/chunk-BYFSK72L.js.map +0 -1
  362. package/dist/chunk-EXUD6RNJ.js +0 -451
  363. package/dist/chunk-EXUD6RNJ.js.map +0 -1
  364. package/dist/chunk-GLK6VM3F.js.map +0 -1
  365. package/dist/chunk-I7PSE6JW.js.map +0 -1
  366. package/dist/chunk-JBKQ3SAO.js.map +0 -1
  367. package/dist/chunk-KNC55RTG.js.map +0 -1
  368. package/dist/chunk-LXQLPRQ2.js.map +0 -1
  369. package/dist/chunk-R77UEZ4E.js.map +0 -1
  370. package/dist/chunk-SQGMNID3.js.map +0 -1
  371. package/dist/chunk-T33XF5ZC.js +0 -12922
  372. package/dist/chunk-T33XF5ZC.js.map +0 -1
  373. package/dist/chunk-XM25TVIE.js.map +0 -1
  374. package/docs/api/classes/ColumnFactory.md +0 -243
  375. package/docs/api/classes/ErrorBoundary.md +0 -144
  376. package/docs/api/classes/InvalidScopeError.md +0 -73
  377. package/docs/api/classes/Logger.md +0 -178
  378. package/docs/api/classes/MissingUserContextError.md +0 -66
  379. package/docs/api/classes/OrganisationContextRequiredError.md +0 -66
  380. package/docs/api/classes/PermissionDeniedError.md +0 -73
  381. package/docs/api/classes/RBACAuditManager.md +0 -297
  382. package/docs/api/classes/RBACCache.md +0 -322
  383. package/docs/api/classes/RBACEngine.md +0 -171
  384. package/docs/api/classes/RBACError.md +0 -76
  385. package/docs/api/classes/RBACNotInitializedError.md +0 -66
  386. package/docs/api/classes/SecureSupabaseClient.md +0 -160
  387. package/docs/api/classes/StorageUtils.md +0 -328
  388. package/docs/api/enums/FileCategory.md +0 -184
  389. package/docs/api/enums/LogLevel.md +0 -54
  390. package/docs/api/enums/RBACErrorCode.md +0 -228
  391. package/docs/api/enums/RPCFunction.md +0 -118
  392. package/docs/api/interfaces/AddressFieldProps.md +0 -241
  393. package/docs/api/interfaces/AddressFieldRef.md +0 -94
  394. package/docs/api/interfaces/AggregateConfig.md +0 -43
  395. package/docs/api/interfaces/AutocompleteOptions.md +0 -75
  396. package/docs/api/interfaces/AvatarProps.md +0 -128
  397. package/docs/api/interfaces/BadgeProps.md +0 -27
  398. package/docs/api/interfaces/ButtonProps.md +0 -53
  399. package/docs/api/interfaces/CalendarProps.md +0 -70
  400. package/docs/api/interfaces/CardProps.md +0 -66
  401. package/docs/api/interfaces/ColorPalette.md +0 -7
  402. package/docs/api/interfaces/ColorShade.md +0 -66
  403. package/docs/api/interfaces/ComplianceResult.md +0 -30
  404. package/docs/api/interfaces/DataAccessRecord.md +0 -96
  405. package/docs/api/interfaces/DataRecord.md +0 -11
  406. package/docs/api/interfaces/DataTableAction.md +0 -249
  407. package/docs/api/interfaces/DataTableColumn.md +0 -504
  408. package/docs/api/interfaces/DataTableProps.md +0 -625
  409. package/docs/api/interfaces/DataTableToolbarButton.md +0 -96
  410. package/docs/api/interfaces/DatabaseComplianceResult.md +0 -85
  411. package/docs/api/interfaces/DatabaseIssue.md +0 -41
  412. package/docs/api/interfaces/EmptyStateConfig.md +0 -61
  413. package/docs/api/interfaces/EnhancedNavigationMenuProps.md +0 -235
  414. package/docs/api/interfaces/EventAppRoleData.md +0 -71
  415. package/docs/api/interfaces/ExportColumn.md +0 -90
  416. package/docs/api/interfaces/ExportOptions.md +0 -126
  417. package/docs/api/interfaces/FileDisplayProps.md +0 -249
  418. package/docs/api/interfaces/FileMetadata.md +0 -129
  419. package/docs/api/interfaces/FileReference.md +0 -118
  420. package/docs/api/interfaces/FileSizeLimits.md +0 -7
  421. package/docs/api/interfaces/FileUploadOptions.md +0 -139
  422. package/docs/api/interfaces/FileUploadProps.md +0 -293
  423. package/docs/api/interfaces/FooterProps.md +0 -105
  424. package/docs/api/interfaces/FormFieldProps.md +0 -166
  425. package/docs/api/interfaces/FormProps.md +0 -113
  426. package/docs/api/interfaces/GrantEventAppRoleParams.md +0 -122
  427. package/docs/api/interfaces/InactivityWarningModalProps.md +0 -115
  428. package/docs/api/interfaces/InputProps.md +0 -53
  429. package/docs/api/interfaces/LabelProps.md +0 -107
  430. package/docs/api/interfaces/LoggerConfig.md +0 -62
  431. package/docs/api/interfaces/LoginFormProps.md +0 -184
  432. package/docs/api/interfaces/NavigationAccessRecord.md +0 -107
  433. package/docs/api/interfaces/NavigationContextType.md +0 -164
  434. package/docs/api/interfaces/NavigationGuardProps.md +0 -139
  435. package/docs/api/interfaces/NavigationItem.md +0 -120
  436. package/docs/api/interfaces/NavigationMenuProps.md +0 -221
  437. package/docs/api/interfaces/NavigationProviderProps.md +0 -117
  438. package/docs/api/interfaces/Organisation.md +0 -140
  439. package/docs/api/interfaces/OrganisationContextType.md +0 -388
  440. package/docs/api/interfaces/OrganisationMembership.md +0 -140
  441. package/docs/api/interfaces/OrganisationProviderProps.md +0 -76
  442. package/docs/api/interfaces/OrganisationSecurityError.md +0 -62
  443. package/docs/api/interfaces/PaceAppLayoutProps.md +0 -406
  444. package/docs/api/interfaces/PaceLoginPageProps.md +0 -47
  445. package/docs/api/interfaces/PageAccessRecord.md +0 -85
  446. package/docs/api/interfaces/PagePermissionContextType.md +0 -140
  447. package/docs/api/interfaces/PagePermissionGuardProps.md +0 -153
  448. package/docs/api/interfaces/PagePermissionProviderProps.md +0 -119
  449. package/docs/api/interfaces/PaletteData.md +0 -41
  450. package/docs/api/interfaces/ParsedAddress.md +0 -120
  451. package/docs/api/interfaces/PermissionEnforcerProps.md +0 -153
  452. package/docs/api/interfaces/ProgressProps.md +0 -42
  453. package/docs/api/interfaces/ProtectedRouteProps.md +0 -97
  454. package/docs/api/interfaces/PublicPageFooterProps.md +0 -112
  455. package/docs/api/interfaces/PublicPageHeaderProps.md +0 -125
  456. package/docs/api/interfaces/PublicPageLayoutProps.md +0 -198
  457. package/docs/api/interfaces/QuickFix.md +0 -52
  458. package/docs/api/interfaces/RBACAccessValidateParams.md +0 -52
  459. package/docs/api/interfaces/RBACAccessValidateResult.md +0 -41
  460. package/docs/api/interfaces/RBACAuditLogParams.md +0 -85
  461. package/docs/api/interfaces/RBACAuditLogResult.md +0 -52
  462. package/docs/api/interfaces/RBACConfig.md +0 -133
  463. package/docs/api/interfaces/RBACContext.md +0 -52
  464. package/docs/api/interfaces/RBACLogger.md +0 -112
  465. package/docs/api/interfaces/RBACPageAccessCheckParams.md +0 -74
  466. package/docs/api/interfaces/RBACPerformanceMetrics.md +0 -138
  467. package/docs/api/interfaces/RBACPermissionCheckParams.md +0 -74
  468. package/docs/api/interfaces/RBACPermissionCheckResult.md +0 -52
  469. package/docs/api/interfaces/RBACPermissionsGetParams.md +0 -63
  470. package/docs/api/interfaces/RBACPermissionsGetResult.md +0 -63
  471. package/docs/api/interfaces/RBACResult.md +0 -58
  472. package/docs/api/interfaces/RBACRoleGrantParams.md +0 -63
  473. package/docs/api/interfaces/RBACRoleGrantResult.md +0 -52
  474. package/docs/api/interfaces/RBACRoleRevokeParams.md +0 -63
  475. package/docs/api/interfaces/RBACRoleRevokeResult.md +0 -52
  476. package/docs/api/interfaces/RBACRoleValidateParams.md +0 -52
  477. package/docs/api/interfaces/RBACRoleValidateResult.md +0 -63
  478. package/docs/api/interfaces/RBACRolesListParams.md +0 -52
  479. package/docs/api/interfaces/RBACRolesListResult.md +0 -74
  480. package/docs/api/interfaces/RBACSessionTrackParams.md +0 -74
  481. package/docs/api/interfaces/RBACSessionTrackResult.md +0 -52
  482. package/docs/api/interfaces/ResourcePermissions.md +0 -155
  483. package/docs/api/interfaces/RevokeEventAppRoleParams.md +0 -100
  484. package/docs/api/interfaces/RoleBasedRouterContextType.md +0 -151
  485. package/docs/api/interfaces/RoleBasedRouterProps.md +0 -156
  486. package/docs/api/interfaces/RoleManagementResult.md +0 -52
  487. package/docs/api/interfaces/RouteAccessRecord.md +0 -107
  488. package/docs/api/interfaces/RouteConfig.md +0 -134
  489. package/docs/api/interfaces/RuntimeComplianceResult.md +0 -55
  490. package/docs/api/interfaces/SecureDataContextType.md +0 -168
  491. package/docs/api/interfaces/SecureDataProviderProps.md +0 -132
  492. package/docs/api/interfaces/SessionRestorationLoaderProps.md +0 -34
  493. package/docs/api/interfaces/SetupIssue.md +0 -41
  494. package/docs/api/interfaces/StorageConfig.md +0 -41
  495. package/docs/api/interfaces/StorageFileInfo.md +0 -74
  496. package/docs/api/interfaces/StorageFileMetadata.md +0 -151
  497. package/docs/api/interfaces/StorageListOptions.md +0 -99
  498. package/docs/api/interfaces/StorageListResult.md +0 -41
  499. package/docs/api/interfaces/StorageUploadOptions.md +0 -101
  500. package/docs/api/interfaces/StorageUploadResult.md +0 -63
  501. package/docs/api/interfaces/StorageUrlOptions.md +0 -60
  502. package/docs/api/interfaces/StyleImport.md +0 -19
  503. package/docs/api/interfaces/SwitchProps.md +0 -34
  504. package/docs/api/interfaces/TabsContentProps.md +0 -9
  505. package/docs/api/interfaces/TabsListProps.md +0 -9
  506. package/docs/api/interfaces/TabsProps.md +0 -9
  507. package/docs/api/interfaces/TabsTriggerProps.md +0 -50
  508. package/docs/api/interfaces/TextareaProps.md +0 -53
  509. package/docs/api/interfaces/ToastActionElement.md +0 -9
  510. package/docs/api/interfaces/ToastProps.md +0 -9
  511. package/docs/api/interfaces/UnifiedAuthContextType.md +0 -820
  512. package/docs/api/interfaces/UnifiedAuthProviderProps.md +0 -171
  513. package/docs/api/interfaces/UseFormDialogOptions.md +0 -62
  514. package/docs/api/interfaces/UseFormDialogReturn.md +0 -117
  515. package/docs/api/interfaces/UseInactivityTrackerOptions.md +0 -136
  516. package/docs/api/interfaces/UseInactivityTrackerReturn.md +0 -123
  517. package/docs/api/interfaces/UsePublicEventLogoOptions.md +0 -87
  518. package/docs/api/interfaces/UsePublicEventLogoReturn.md +0 -81
  519. package/docs/api/interfaces/UsePublicEventOptions.md +0 -34
  520. package/docs/api/interfaces/UsePublicEventReturn.md +0 -68
  521. package/docs/api/interfaces/UsePublicFileDisplayOptions.md +0 -47
  522. package/docs/api/interfaces/UsePublicFileDisplayReturn.md +0 -120
  523. package/docs/api/interfaces/UsePublicRouteParamsReturn.md +0 -94
  524. package/docs/api/interfaces/UseResolvedScopeOptions.md +0 -47
  525. package/docs/api/interfaces/UseResolvedScopeReturn.md +0 -47
  526. package/docs/api/interfaces/UseResourcePermissionsOptions.md +0 -34
  527. package/docs/api/interfaces/UserEventAccess.md +0 -118
  528. package/docs/api/interfaces/UserMenuProps.md +0 -86
  529. package/docs/api/interfaces/UserProfile.md +0 -63
  530. package/docs/migration/quick-migration-guide.md +0 -356
  531. package/docs/migration/service-architecture.md +0 -281
  532. package/src/components/EventSelector/EventSelector.test.tsx +0 -720
  533. package/src/components/EventSelector/EventSelector.tsx +0 -420
  534. package/src/components/EventSelector/index.ts +0 -3
  535. package/src/components/OrganisationSelector/OrganisationSelector.test.tsx +0 -784
  536. package/src/components/OrganisationSelector/OrganisationSelector.tsx +0 -324
  537. package/src/components/OrganisationSelector/index.ts +0 -9
  538. package/src/hooks/__tests__/useSecureDataAccess.unit.test.tsx +0 -680
  539. package/src/hooks/useSecureDataAccess.test.ts +0 -559
  540. package/src/hooks/useSecureDataAccess.ts +0 -681
  541. /package/dist/{DataTable-DQ7RSOHE.js.map → DataTable-THFPBKTP.js.map} +0 -0
  542. /package/dist/{UnifiedAuthProvider-ATAP5UTR.js.map → UnifiedAuthProvider-KAGUYQ4J.js.map} +0 -0
  543. /package/dist/{api-N774RPUA.js.map → api-IAGWF3ZG.js.map} +0 -0
  544. /package/dist/{audit-B5P6FFIR.js.map → audit-V53FV5AG.js.map} +0 -0
  545. /package/dist/{chunk-7D4SUZUM.js.map → chunk-DGUM43GV.js.map} +0 -0
  546. /package/docs/migration/{organisation-context-timing-fix.md → V0.3.44_organisation-context-timing-fix.md} +0 -0
  547. /package/docs/migration/{rbac-migration.md → V0.4.0_rbac-migration.md} +0 -0
  548. /package/docs/migration/{person-scoped-profiles-migration-guide.md → V0.5.190_person-scoped-profiles-migration-guide.md} +0 -0
  549. /package/docs/migration/{REACT_19_MIGRATION.md → V0.6.0_REACT_19_MIGRATION.md} +0 -0
@@ -0,0 +1,985 @@
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
+ // Check if package is imported in source files (runtime)
652
+ if (importedPackages.has(dep)) {
653
+ // Package is used in runtime code, so it's needed
654
+ return;
655
+ }
656
+
657
+ // Check if it's used in package.json scripts
658
+ if (checkPackageScripts(packageJson, dep)) {
659
+ return;
660
+ }
661
+
662
+ // Check if it's used in config files
663
+ if (checkConfigFiles(configFiles, dep)) {
664
+ return;
665
+ }
666
+
667
+ // Check if it's used in CSS or other non-JS files
668
+ if (checkNonJsFiles(projectRoot, dep)) {
669
+ return;
670
+ }
671
+
672
+ // Check if it's used in test files
673
+ const isUsedInTests = testOnlyPackages.has(dep) || isOnlyUsedInTests(projectRoot, dep, testFiles);
674
+ const isOnlyInTests = isUsedInTests && !importedPackages.has(dep);
675
+
676
+ // For dev dependencies, provide more context
677
+ const isDevDep = dep in devDeps;
678
+ const context = [];
679
+
680
+ if (isDevDep) {
681
+ context.push('This is a dev dependency and may be used by build tools or test frameworks');
682
+ }
683
+
684
+ // Check if it might be a transitive dependency needed by pace-core
685
+ if (paceCoreVersion) {
686
+ context.push('This may be required by @jmruthers/pace-core as a transitive dependency');
687
+ }
688
+
689
+ // Check if it's in vite.config.ts optimizeDeps (common for pace-core peer deps)
690
+ const viteConfigPath = path.join(projectRoot, 'vite.config.ts');
691
+ const viteConfigJsPath = path.join(projectRoot, 'vite.config.js');
692
+ let isInViteConfig = false;
693
+
694
+ for (const configPath of [viteConfigPath, viteConfigJsPath]) {
695
+ if (fs.existsSync(configPath)) {
696
+ try {
697
+ const viteContent = fs.readFileSync(configPath, 'utf8');
698
+ if (viteContent.includes(dep)) {
699
+ isInViteConfig = true;
700
+ break;
701
+ }
702
+ } catch (error) {
703
+ // Skip if can't read
704
+ }
705
+ }
706
+ }
707
+
708
+ // Only warn if we're reasonably sure it's unused
709
+ // For runtime dependencies, be more strict
710
+ if (!isDevDep) {
711
+ // If it's only used in tests, suggest moving to devDependencies
712
+ if (isOnlyInTests) {
713
+ warnings.push({
714
+ type: 'test-only-runtime-dependency',
715
+ file: 'package.json',
716
+ message: `Runtime dependency '${dep}' is only used in test files`,
717
+ recommendation: `Move '${dep}' from dependencies to devDependencies since it's only used in tests. This will reduce your production bundle size.`
718
+ });
719
+ return;
720
+ }
721
+
722
+ 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.`;
723
+
724
+ warnings.push({
725
+ type: 'unused-dependency',
726
+ file: 'package.json',
727
+ message: `Runtime dependency '${dep}' may be unused`,
728
+ recommendation: recommendation
729
+ });
730
+ } else {
731
+ // For dev dependencies, only suggest (lower severity)
732
+ suggestions.push({
733
+ type: 'potentially-unused-dev-dependency',
734
+ file: 'package.json',
735
+ message: `Dev dependency '${dep}' may be unused`,
736
+ 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.`
737
+ });
738
+ }
739
+ });
740
+ }
741
+
742
+ // Check for missing peer dependencies from pace-core
743
+ Object.keys(paceCorePeerDeps).forEach(peerDep => {
744
+ if (!allDeps[peerDep]) {
745
+ warnings.push({
746
+ type: 'missing-peer-dependency',
747
+ file: 'package.json',
748
+ message: `Peer dependency '${peerDep}' is missing but required by @jmruthers/pace-core`,
749
+ recommendation: `Install '${peerDep}' as it's required by pace-core. Run: npm install ${peerDep}`
750
+ });
751
+ }
752
+ });
753
+
754
+ // Check for version mismatches between react and react-dom
755
+ // (The required version check is done earlier in the function)
756
+ const reactVersionForMismatch = packageJson.dependencies?.react || packageJson.devDependencies?.react;
757
+ const reactDomVersionForMismatch = packageJson.dependencies?.['react-dom'] || packageJson.devDependencies?.['react-dom'];
758
+
759
+ if (reactVersionForMismatch && reactDomVersionForMismatch && reactVersionForMismatch !== reactDomVersionForMismatch) {
760
+ issues.push({
761
+ type: 'version-mismatch',
762
+ file: 'package.json',
763
+ message: `React version mismatch: react@${reactVersionForMismatch} vs react-dom@${reactDomVersionForMismatch}`,
764
+ recommendation: 'React and react-dom versions must match exactly'
765
+ });
766
+ }
767
+
768
+ // ============================================
769
+ // NEW CHECKS: Future-proofing dependency management
770
+ // ============================================
771
+
772
+ // 1. Check for redundant dependencies in consuming apps
773
+ // (packages already provided by pace-core)
774
+ if (!isPaceCoreRepository) {
775
+ const paceCoreDeps = getPaceCoreDeps(projectRoot);
776
+ const paceCorePeerDeps = getPaceCorePeerDeps(projectRoot);
777
+
778
+ // Packages that pace-core provides as dependencies (should not be duplicated)
779
+ const redundantDeps = [];
780
+ Object.keys(packageJson.dependencies || {}).forEach(dep => {
781
+ // Skip pace-core itself
782
+ if (dep === '@jmruthers/pace-core') {
783
+ return;
784
+ }
785
+
786
+ // Check if it's provided by pace-core as a dependency
787
+ if (paceCoreDeps[dep]) {
788
+ redundantDeps.push({
789
+ dep,
790
+ reason: 'provided by pace-core as a dependency',
791
+ paceCoreVersion: paceCoreDeps[dep]
792
+ });
793
+ }
794
+ });
795
+
796
+ redundantDeps.forEach(({ dep, reason, paceCoreVersion }) => {
797
+ warnings.push({
798
+ type: 'redundant-dependency',
799
+ file: 'package.json',
800
+ message: `Dependency '${dep}' is redundant - ${reason}`,
801
+ 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.`
802
+ });
803
+ });
804
+ }
805
+
806
+ // 2. Check for missing dependencies (used but not declared)
807
+ if (!isPaceCoreRepository) {
808
+ // Check for dynamic imports that might need to be declared
809
+ const optionalDeps = ['recharts', 'papaparse', 'lodash.debounce', 'lodash.throttle'];
810
+ const dynamicImportPattern = /import\(['"]([^'"]+)['"]\)/g;
811
+
812
+ sourceFiles.forEach(filePath => {
813
+ try {
814
+ const content = fs.readFileSync(filePath, 'utf8');
815
+ let match;
816
+ while ((match = dynamicImportPattern.exec(content)) !== null) {
817
+ const modulePath = match[1];
818
+ const pkgName = extractPackageName(modulePath);
819
+ if (pkgName && optionalDeps.includes(pkgName)) {
820
+ // Check if it's declared
821
+ if (!allDeps[pkgName]) {
822
+ suggestions.push({
823
+ type: 'missing-optional-dependency',
824
+ file: path.relative(projectRoot, filePath),
825
+ message: `Optional dependency '${pkgName}' is dynamically imported but not declared`,
826
+ recommendation: `Add '${pkgName}' to dependencies if you use this feature, or ensure it's marked as external in your build config`
827
+ });
828
+ }
829
+ }
830
+ }
831
+ } catch (error) {
832
+ // Skip files with errors
833
+ }
834
+ });
835
+ }
836
+
837
+ // 3. Check for misclassified dependencies
838
+ // Runtime deps in devDependencies, peer deps in dependencies, etc.
839
+ if (isPaceCoreRepository) {
840
+ // Check pace-core's own package.json
841
+ const packagesCorePath = path.join(projectRoot, 'packages', 'core', 'package.json');
842
+ if (fs.existsSync(packagesCorePath)) {
843
+ const corePkg = JSON.parse(fs.readFileSync(packagesCorePath, 'utf8'));
844
+ const coreDeps = corePkg.dependencies || {};
845
+ const coreDevDeps = corePkg.devDependencies || {};
846
+ const corePeerDeps = corePkg.peerDependencies || {};
847
+
848
+ // Check if peer dependencies are incorrectly in dependencies
849
+ Object.keys(coreDeps).forEach(dep => {
850
+ if (corePeerDeps[dep]) {
851
+ issues.push({
852
+ type: 'misclassified-dependency',
853
+ file: 'packages/core/package.json',
854
+ message: `'${dep}' is declared as both dependency and peerDependency`,
855
+ recommendation: `Remove '${dep}' from dependencies. Peer dependencies should only be in peerDependencies.`
856
+ });
857
+ }
858
+ });
859
+
860
+ // Check if runtime dependencies are in devDependencies
861
+ // This is harder to detect automatically, but we can check for common patterns
862
+ const runtimeDepsInDev = ['@hookform/resolvers', '@supabase/supabase-js', '@tanstack/react-query'];
863
+ runtimeDepsInDev.forEach(dep => {
864
+ if (coreDevDeps[dep] && !coreDeps[dep]) {
865
+ // Check if it's used in source code
866
+ const coreSrcPath = path.join(projectRoot, 'packages', 'core', 'src');
867
+ if (fs.existsSync(coreSrcPath)) {
868
+ const coreSourceFiles = findSourceFiles(coreSrcPath);
869
+ let isUsed = false;
870
+ coreSourceFiles.forEach(filePath => {
871
+ try {
872
+ const content = fs.readFileSync(filePath, 'utf8');
873
+ if (content.includes(dep) || content.includes(extractPackageName(dep))) {
874
+ isUsed = true;
875
+ }
876
+ } catch (error) {
877
+ // Skip
878
+ }
879
+ });
880
+
881
+ if (isUsed) {
882
+ warnings.push({
883
+ type: 'misclassified-dependency',
884
+ file: 'packages/core/package.json',
885
+ message: `'${dep}' is in devDependencies but appears to be used in source code`,
886
+ recommendation: `Move '${dep}' from devDependencies to dependencies if it's used at runtime`
887
+ });
888
+ }
889
+ }
890
+ }
891
+ });
892
+ }
893
+ }
894
+
895
+ // 4. Check for version mismatches across workspace (monorepo)
896
+ if (isPaceCoreRepository) {
897
+ const rootPkg = getWorkspaceRootPackageJson(projectRoot);
898
+ const corePkg = getPaceCorePackageJson(projectRoot);
899
+
900
+ if (rootPkg && corePkg) {
901
+ const rootDeps = { ...rootPkg.dependencies, ...rootPkg.devDependencies };
902
+ const coreDeps = { ...corePkg.dependencies, ...corePkg.devDependencies };
903
+
904
+ // Check for version mismatches in common dependencies
905
+ const commonDeps = Object.keys(rootDeps).filter(dep => coreDeps[dep]);
906
+ commonDeps.forEach(dep => {
907
+ const rootVersion = rootDeps[dep];
908
+ const coreVersion = coreDeps[dep];
909
+
910
+ // Normalize versions for comparison (remove ^, ~, etc.)
911
+ const normalizeVersion = (v) => v.replace(/^[\^~]/, '');
912
+ const rootNormalized = normalizeVersion(rootVersion);
913
+ const coreNormalized = normalizeVersion(coreVersion);
914
+
915
+ if (rootNormalized !== coreNormalized) {
916
+ warnings.push({
917
+ type: 'workspace-version-mismatch',
918
+ file: 'package.json',
919
+ message: `Version mismatch for '${dep}': root@${rootVersion} vs packages/core@${coreVersion}`,
920
+ recommendation: `Align versions across workspace. Consider using the same version in both root and packages/core package.json files.`
921
+ });
922
+ }
923
+ });
924
+ }
925
+ }
926
+
927
+ // 5. Check for missing peer dependencies in consuming apps
928
+ if (!isPaceCoreRepository) {
929
+ const paceCorePkg = getPaceCorePackageJson(projectRoot);
930
+ if (paceCorePkg) {
931
+ const peerDeps = paceCorePkg.peerDependencies || {};
932
+ const allDeps = { ...packageJson.dependencies, ...packageJson.devDependencies };
933
+
934
+ Object.keys(peerDeps).forEach(peerDep => {
935
+ if (!allDeps[peerDep]) {
936
+ issues.push({
937
+ type: 'missing-peer-dependency',
938
+ file: 'package.json',
939
+ message: `Missing peer dependency '${peerDep}' required by @jmruthers/pace-core`,
940
+ recommendation: `Install '${peerDep}@${peerDeps[peerDep]}' as a dependency. Run: npm install ${peerDep}@${peerDeps[peerDep]}`
941
+ });
942
+ }
943
+ });
944
+ }
945
+ }
946
+
947
+ // 6. Check for optional dependencies handling in vite.config
948
+ if (!isPaceCoreRepository) {
949
+ const viteConfigPath = path.join(projectRoot, 'vite.config.ts');
950
+ const viteConfigJsPath = path.join(projectRoot, 'vite.config.js');
951
+ const optionalDeps = ['recharts', 'papaparse'];
952
+
953
+ for (const configPath of [viteConfigPath, viteConfigJsPath]) {
954
+ if (fs.existsSync(configPath)) {
955
+ try {
956
+ const viteContent = fs.readFileSync(configPath, 'utf8');
957
+ optionalDeps.forEach(dep => {
958
+ // Check if it's marked as external
959
+ const isExternal = viteContent.includes(`external`) &&
960
+ (viteContent.includes(`'${dep}'`) || viteContent.includes(`"${dep}"`));
961
+
962
+ // Check if it's in dependencies
963
+ const isInDeps = allDeps[dep];
964
+
965
+ if (!isExternal && !isInDeps) {
966
+ suggestions.push({
967
+ type: 'optional-dependency-config',
968
+ file: path.relative(projectRoot, configPath),
969
+ message: `Optional dependency '${dep}' should be handled in build config`,
970
+ 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`
971
+ });
972
+ }
973
+ });
974
+ } catch (error) {
975
+ // Skip if can't read
976
+ }
977
+ }
978
+ }
979
+ }
980
+
981
+ return { issues, warnings, suggestions };
982
+ }
983
+ };
984
+
985
+ module.exports = dependenciesCheck;