@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
@@ -7,11 +7,17 @@
7
7
  * significantly speeding up the build process.
8
8
  */
9
9
 
10
- import { execSync } from 'child_process';
11
- import { existsSync, statSync, readFileSync, writeFileSync, readdirSync, mkdirSync, rmSync, cpSync, utimesSync } from 'fs';
12
- import { join, dirname, relative } from 'path';
10
+ import { join, dirname } from 'path';
13
11
  import { fileURLToPath } from 'url';
14
- import { createHash } from 'crypto';
12
+ import { existsSync } from 'fs';
13
+
14
+ // Import modules
15
+ import { scanDirectory, getLatestSourceModTime, getLatestDocModTime } from './build-docs/file-utils.js';
16
+ import { shouldSkipDueToCleanGitState, getGitStatus } from './build-docs/git-utils.js';
17
+ import { getSourceFileHashes, hasSourceChanged, hashesMatch, getExistingDocHashes } from './build-docs/hash-utils.js';
18
+ import { loadCache, saveCache, primeCacheFromExistingDocs } from './build-docs/cache-utils.js';
19
+ import { shouldRunTypeDoc, needsRebuild } from './build-docs/build-decision.js';
20
+ import { runTypeDoc } from './build-docs/typedoc-runner.js';
15
21
 
16
22
  const __filename = fileURLToPath(import.meta.url);
17
23
  const __dirname = dirname(__filename);
@@ -27,900 +33,13 @@ const DOCS_RELEVANT_PATHS = [
27
33
  'packages/core/typedoc.json'
28
34
  ];
29
35
 
30
- /**
31
- * Recursively scan directory for files matching pattern
32
- */
33
- function scanDirectory(dir, extensions, ignorePatterns = []) {
34
- const files = [];
35
-
36
- function scan(currentDir) {
37
- if (!existsSync(currentDir)) {
38
- return;
39
- }
40
-
41
- try {
42
- const items = readdirSync(currentDir, { withFileTypes: true });
43
-
44
- for (const item of items) {
45
- const fullPath = join(currentDir, item.name);
46
-
47
- // Skip ignored patterns
48
- const shouldIgnore = ignorePatterns.some(pattern => {
49
- const relativePath = fullPath.replace(SRC_DIR + '/', '');
50
- return pattern.test(relativePath);
51
- });
52
-
53
- if (shouldIgnore) {
54
- continue;
55
- }
56
-
57
- if (item.isDirectory() && !item.name.startsWith('.') && item.name !== 'node_modules') {
58
- scan(fullPath);
59
- } else if (item.isFile()) {
60
- const ext = item.name.substring(item.name.lastIndexOf('.'));
61
- if (extensions.includes(ext)) {
62
- files.push(fullPath);
63
- }
64
- }
65
- }
66
- } catch (err) {
67
- // Skip directories we can't read
68
- }
69
- }
70
-
71
- scan(dir);
72
- return files;
73
- }
74
-
75
- /**
76
- * Determine if we're inside a git repository
77
- */
78
- function isGitRepository() {
79
- try {
80
- execSync('git rev-parse --is-inside-work-tree', {
81
- cwd: REPO_ROOT,
82
- stdio: 'pipe'
83
- });
84
- return true;
85
- } catch (err) {
86
- return false;
87
- }
88
- }
89
-
90
- /**
91
- * Check if git reports any relevant source changes
92
- */
93
- function hasGitTrackedSourceChanges() {
94
- if (!isGitRepository()) {
95
- return true; // Without git context, err on the side of rebuilding
96
- }
97
-
98
- try {
99
- const status = execSync(`git status --porcelain -- ${DOCS_RELEVANT_PATHS.join(' ')}`.trim(), {
100
- cwd: REPO_ROOT,
101
- encoding: 'utf-8',
102
- stdio: 'pipe'
103
- }).trim();
104
-
105
- return status.length > 0;
106
- } catch (err) {
107
- console.log('⚠️ Unable to determine git status for docs generation, running TypeDoc to be safe.');
108
- return true;
109
- }
110
- }
111
-
112
- /**
113
- * If we don't have a cache yet but the repo is clean and docs exist, we can skip
114
- */
115
- function shouldSkipDueToCleanGitState() {
116
- if (!existsSync(DOCS_DIR)) {
117
- return false;
118
- }
119
-
120
- const existingDocs = scanDirectory(DOCS_DIR, ['.md']);
121
- if (existingDocs.length === 0) {
122
- return false;
123
- }
124
-
125
- if (hasGitTrackedSourceChanges()) {
126
- return false;
127
- }
128
-
129
- console.log('✅ Git working tree is clean for documentation-related sources.');
130
- console.log(' Assuming checked-in docs are up to date, skipping TypeDoc run.');
131
- return true;
132
- }
133
-
134
- /**
135
- * Prime the docs cache using the currently checked-in documentation.
136
- * This lets future runs rely on hash comparisons even if git status is dirty.
137
- */
138
- function primeCacheFromExistingDocs() {
139
- const currentDocHashes = getExistingDocHashes();
140
- const currentSourceHashes = getSourceFileHashes();
141
-
142
- const docHashesObj = {};
143
- for (const [relativePath, hash] of currentDocHashes.entries()) {
144
- docHashesObj[relativePath] = hash;
145
- }
146
-
147
- const sourceHashesObj = {};
148
- for (const [relativePath, hash] of currentSourceHashes.entries()) {
149
- sourceHashesObj[relativePath] = hash;
150
- }
151
-
152
- const cache = {
153
- lastBuildTime: Date.now(),
154
- sourceModTime: getLatestSourceModTime(),
155
- docModTime: getLatestDocModTime(),
156
- fileHashes: docHashesObj,
157
- sourceFileHashes: sourceHashesObj
158
- };
159
-
160
- saveCache(cache);
161
- }
162
-
163
- /**
164
- * Get the most recent modification time of all source files
165
- */
166
- function getLatestSourceModTime() {
167
- const ignorePatterns = [
168
- /\.test\.(ts|tsx)$/,
169
- /\.spec\.(ts|tsx)$/,
170
- /__tests__/,
171
- /\/test\//,
172
- /\/tests\//
173
- ];
174
-
175
- const sourceFiles = scanDirectory(SRC_DIR, ['.ts', '.tsx'], ignorePatterns);
176
-
177
- let latestTime = 0;
178
- for (const filePath of sourceFiles) {
179
- try {
180
- const stats = statSync(filePath);
181
- if (stats.mtimeMs > latestTime) {
182
- latestTime = stats.mtimeMs;
183
- }
184
- } catch (err) {
185
- // File might have been deleted, skip it
186
- }
187
- }
188
-
189
- return latestTime;
190
- }
191
-
192
- /**
193
- * Get the most recent modification time of generated docs
194
- */
195
- function getLatestDocModTime() {
196
- if (!existsSync(DOCS_DIR)) {
197
- return 0;
198
- }
199
-
200
- const docFiles = scanDirectory(DOCS_DIR, ['.md']);
201
-
202
- if (docFiles.length === 0) {
203
- return 0;
204
- }
205
-
206
- let latestTime = 0;
207
- for (const filePath of docFiles) {
208
- try {
209
- const stats = statSync(filePath);
210
- if (stats.mtimeMs > latestTime) {
211
- latestTime = stats.mtimeMs;
212
- }
213
- } catch (err) {
214
- // File might have been deleted, skip it
215
- }
216
- }
217
-
218
- return latestTime;
219
- }
220
-
221
- /**
222
- * Get hashes of all source files
223
- */
224
- function getSourceFileHashes() {
225
- const ignorePatterns = [
226
- /\.test\.(ts|tsx)$/,
227
- /\.spec\.(ts|tsx)$/,
228
- /__tests__/,
229
- /\/test\//,
230
- /\/tests\//
231
- ];
232
-
233
- const sourceFiles = scanDirectory(SRC_DIR, ['.ts', '.tsx'], ignorePatterns);
234
- const hashes = new Map();
235
-
236
- for (const filePath of sourceFiles) {
237
- try {
238
- const content = readFileSync(filePath, 'utf-8');
239
- const hash = createHash('md5').update(content).digest('hex');
240
- const relativePath = relative(SRC_DIR, filePath);
241
- hashes.set(relativePath, hash);
242
- } catch (err) {
243
- // Skip files we can't read
244
- }
245
- }
246
-
247
- return hashes;
248
- }
249
-
250
- /**
251
- * Check if source files have changed by comparing hashes
252
- */
253
- function hasSourceChanged(currentHashes, cachedHashes) {
254
- if (!cachedHashes || Object.keys(cachedHashes).length === 0) {
255
- return true; // No cache, assume changed
256
- }
257
-
258
- // Check if any file changed
259
- for (const [relativePath, currentHash] of currentHashes.entries()) {
260
- const cachedHash = cachedHashes[relativePath];
261
- if (cachedHash !== currentHash) {
262
- return true;
263
- }
264
- }
265
-
266
- // Check if any files were added or removed
267
- if (currentHashes.size !== Object.keys(cachedHashes).length) {
268
- return true;
269
- }
270
-
271
- return false;
272
- }
273
-
274
- /**
275
- * Check if doc file hashes match cached hashes
276
- */
277
- function hashesMatch(currentHashes, cachedHashes) {
278
- if (!cachedHashes || Object.keys(cachedHashes).length === 0) {
279
- return false; // No cache, assume mismatch
280
- }
281
-
282
- if (currentHashes.size !== Object.keys(cachedHashes).length) {
283
- return false; // Different number of files
284
- }
285
-
286
- for (const [relativePath, currentHash] of currentHashes.entries()) {
287
- const cachedHash = cachedHashes[relativePath];
288
- if (cachedHash !== currentHash) {
289
- return false;
290
- }
291
- }
292
-
293
- return true;
294
- }
295
-
296
- /**
297
- * Load the cache file
298
- */
299
- function loadCache() {
300
- if (!existsSync(CACHE_FILE)) {
301
- return {
302
- lastBuildTime: 0,
303
- sourceModTime: 0,
304
- docModTime: 0,
305
- fileHashes: {},
306
- sourceFileHashes: {}
307
- };
308
- }
309
-
310
- try {
311
- const content = readFileSync(CACHE_FILE, 'utf-8');
312
- const cache = JSON.parse(content);
313
- // Ensure fileHashes exists for backward compatibility
314
- if (!cache.fileHashes) {
315
- cache.fileHashes = {};
316
- }
317
- if (!cache.sourceFileHashes) {
318
- cache.sourceFileHashes = {};
319
- }
320
- return cache;
321
- } catch (err) {
322
- return {
323
- lastBuildTime: 0,
324
- sourceModTime: 0,
325
- docModTime: 0,
326
- fileHashes: {},
327
- sourceFileHashes: {}
328
- };
329
- }
330
- }
331
-
332
- /**
333
- * Save the cache file
334
- */
335
- function saveCache(cache) {
336
- // Ensure fileHashes is always present
337
- if (!cache.fileHashes) {
338
- cache.fileHashes = {};
339
- }
340
- if (!cache.sourceFileHashes) {
341
- cache.sourceFileHashes = {};
342
- }
343
- writeFileSync(CACHE_FILE, JSON.stringify(cache, null, 2), 'utf-8');
344
- }
345
-
346
- /**
347
- * Aggressive pre-check: Should we run TypeDoc at all?
348
- * This checks source file hashes BEFORE running TypeDoc to avoid unnecessary runs
349
- */
350
- function shouldRunTypeDoc() {
351
- const cache = loadCache();
352
-
353
- // If no cache, must run
354
- if (cache.lastBuildTime === 0) {
355
- if (shouldSkipDueToCleanGitState()) {
356
- console.log('⏭️ Skipping TypeDoc - no cache yet but git shows no relevant source changes.');
357
- console.log(' Reason: Using checked-in docs since source tree matches HEAD');
358
- console.log(' Action: Priming docs cache from checked-in files for future runs');
359
- primeCacheFromExistingDocs();
360
- return false;
361
- }
362
- console.log('📝 No cache found, running TypeDoc...');
363
- console.log(' Reason: First run - no cache exists');
364
- return true;
365
- }
366
-
367
- // If docs don't exist, must run
368
- if (!existsSync(DOCS_DIR)) {
369
- console.log('📝 Documentation directory not found, running TypeDoc...');
370
- console.log(' Reason: Documentation directory missing');
371
- return true;
372
- }
373
-
374
- // Check source file hashes - most reliable check
375
- console.log('🔍 Checking source file hashes...');
376
- const currentSourceHashes = getSourceFileHashes();
377
- const cachedSourceHashes = cache.sourceFileHashes || {};
378
-
379
- console.log(` Current source files: ${currentSourceHashes.size}`);
380
- console.log(` Cached source files: ${Object.keys(cachedSourceHashes).length}`);
381
-
382
- if (hasSourceChanged(currentSourceHashes, cachedSourceHashes)) {
383
- // Find which files changed
384
- const changedFiles = [];
385
- for (const [relativePath, currentHash] of currentSourceHashes.entries()) {
386
- const cachedHash = cachedSourceHashes[relativePath];
387
- if (cachedHash !== currentHash) {
388
- changedFiles.push(relativePath);
389
- }
390
- }
391
- console.log('📝 Source files have changed (hash comparison), running TypeDoc...');
392
- if (changedFiles.length > 0 && changedFiles.length <= 10) {
393
- console.log(` Changed files: ${changedFiles.slice(0, 5).join(', ')}${changedFiles.length > 5 ? ` ... and ${changedFiles.length - 5} more` : ''}`);
394
- } else if (changedFiles.length > 10) {
395
- console.log(` Changed files: ${changedFiles.length} files modified`);
396
- }
397
- return true;
398
- }
399
-
400
- // Source hasn't changed - check if docs match cache
401
- console.log('🔍 Checking documentation file hashes...');
402
- const currentDocHashes = getExistingDocHashes();
403
- const cachedDocHashes = cache.fileHashes || {};
404
-
405
- console.log(` Current doc files: ${currentDocHashes.size}`);
406
- console.log(` Cached doc files: ${Object.keys(cachedDocHashes).length}`);
407
-
408
- if (hashesMatch(currentDocHashes, cachedDocHashes)) {
409
- console.log('✅ Source unchanged and docs match cache - skipping TypeDoc entirely');
410
- console.log(' Reason: No source changes detected and documentation is up to date');
411
- return false; // Skip TypeDoc!
412
- }
413
-
414
- // Docs don't match cache but source unchanged - refresh cache snapshot instead of regenerating
415
- const mismatchedFiles = [];
416
- for (const [relativePath, currentHash] of currentDocHashes.entries()) {
417
- const cachedHash = cachedDocHashes[relativePath];
418
- if (cachedHash !== currentHash) {
419
- mismatchedFiles.push(relativePath);
420
- }
421
- }
422
- console.log('⚠️ Source unchanged but docs don\'t match cache - refreshing cache from disk');
423
- console.log(` Mismatched files: ${mismatchedFiles.length} files don't match cache`);
424
- if (mismatchedFiles.length <= 5) {
425
- console.log(` Files: ${mismatchedFiles.join(', ')}`);
426
- }
427
- primeCacheFromExistingDocs();
428
- return false;
429
- }
430
-
431
- /**
432
- * Check if any source files have changed since last build
433
- */
434
- function needsRebuild() {
435
- const cache = loadCache();
436
- const currentSourceModTime = getLatestSourceModTime();
437
- const currentDocModTime = getLatestDocModTime();
438
-
439
- // If docs don't exist, we need to build
440
- if (!existsSync(DOCS_DIR) || currentDocModTime === 0) {
441
- console.log('📝 Documentation directory not found or empty, building...');
442
- return true;
443
- }
444
-
445
- // Check if typedoc.json has changed
446
- const typedocConfigPath = join(PACKAGE_ROOT, 'typedoc.json');
447
- if (existsSync(typedocConfigPath)) {
448
- const configStats = statSync(typedocConfigPath);
449
- if (cache.lastBuildTime > 0 && configStats.mtimeMs > cache.lastBuildTime) {
450
- console.log('📝 TypeDoc configuration has changed, rebuilding...');
451
- return true;
452
- }
453
- if (currentDocModTime > 0 && configStats.mtimeMs > currentDocModTime) {
454
- console.log('📝 TypeDoc configuration is newer than docs, rebuilding...');
455
- return true;
456
- }
457
- }
458
-
459
- // Check entry point file (what TypeDoc actually uses)
460
- const entryPointPath = join(PACKAGE_ROOT, 'src', 'index.ts');
461
- if (existsSync(entryPointPath)) {
462
- const entryStats = statSync(entryPointPath);
463
- if (cache.lastBuildTime > 0 && entryStats.mtimeMs > cache.lastBuildTime) {
464
- console.log('📝 Entry point file has changed, rebuilding...');
465
- return true;
466
- }
467
- if (currentDocModTime > 0 && entryStats.mtimeMs > currentDocModTime) {
468
- console.log('📝 Entry point file is newer than docs, rebuilding...');
469
- return true;
470
- }
471
- }
472
-
473
- // If source files are newer than docs, we need to rebuild
474
- if (currentSourceModTime > currentDocModTime) {
475
- console.log('📝 Source files are newer than documentation, rebuilding...');
476
- return true;
477
- }
478
-
479
- // If cache indicates a rebuild is needed (e.g., after a clean)
480
- // Only check cache if we have a valid cache entry
481
- if (cache.lastBuildTime > 0) {
482
- // Check if source files have changed since last build
483
- const sourceChanged = currentSourceModTime > cache.sourceModTime;
484
-
485
- // Check if docs are newer than the last build time (meaning they were just built)
486
- const docsJustBuilt = currentDocModTime > cache.lastBuildTime - 1000; // 1 second tolerance
487
-
488
- if (sourceChanged) {
489
- console.log('📝 Source files have changed since last build, rebuilding...');
490
- return true;
491
- }
492
-
493
- // If docs were just built (within last second), skip rebuild to avoid double-building
494
- if (docsJustBuilt) {
495
- console.log('✅ Documentation was just built, skipping rebuild to avoid double-build');
496
- return false;
497
- }
498
-
499
- // Check file-level hashes if available
500
- if (cache.fileHashes && Object.keys(cache.fileHashes).length > 0) {
501
- const currentHashes = getExistingDocHashes();
502
- let filesChanged = false;
503
-
504
- // Check if any file hashes changed
505
- for (const [relativePath, cachedHash] of Object.entries(cache.fileHashes)) {
506
- const currentHash = currentHashes.get(relativePath);
507
- if (currentHash !== cachedHash) {
508
- filesChanged = true;
509
- break;
510
- }
511
- }
512
-
513
- // Check if any new files were added
514
- if (!filesChanged && currentHashes.size !== Object.keys(cache.fileHashes).length) {
515
- filesChanged = true;
516
- }
517
-
518
- if (!filesChanged && !sourceChanged) {
519
- console.log('✅ Documentation is up to date (file hashes match cache), skipping rebuild');
520
- return false;
521
- }
522
- }
523
-
524
- // If cache exists and source hasn't changed, we're good
525
- console.log('✅ Documentation is up to date (cache valid), skipping rebuild');
526
- return false;
527
- }
528
-
529
- // No cache exists - compare source vs docs directly
530
- if (currentSourceModTime <= currentDocModTime) {
531
- console.log('✅ Documentation is up to date, skipping rebuild');
532
- return false;
533
- }
534
-
535
- // Fallback: rebuild if we can't determine
536
- console.log('📝 Unable to determine if rebuild needed, rebuilding to be safe...');
537
- return true;
538
- }
539
-
540
- /**
541
- * Get current package version from package.json
542
- */
543
- function getPackageVersion() {
544
- try {
545
- const packageJsonPath = join(PACKAGE_ROOT, 'package.json');
546
- const packageJson = JSON.parse(readFileSync(packageJsonPath, 'utf-8'));
547
- return packageJson.version || '';
548
- } catch (err) {
549
- return '';
550
- }
551
- }
552
-
553
- /**
554
- * Sort markdown list items for deterministic comparison
555
- */
556
- function sortMarkdownListItems(text) {
557
- // Match markdown list sections (lines starting with - or *)
558
- const lines = text.split('\n');
559
- const sortedLines = [];
560
- let currentList = [];
561
- let inList = false;
562
-
563
- for (const line of lines) {
564
- const isListItem = /^\s*[-*]\s+/.test(line);
565
-
566
- if (isListItem) {
567
- if (!inList && currentList.length > 0) {
568
- // Flush previous non-list content
569
- sortedLines.push(...currentList);
570
- currentList = [];
571
- }
572
- inList = true;
573
- currentList.push(line);
574
- } else {
575
- if (inList && currentList.length > 0) {
576
- // Sort and flush list
577
- currentList.sort();
578
- sortedLines.push(...currentList);
579
- currentList = [];
580
- }
581
- inList = false;
582
- currentList.push(line);
583
- }
584
- }
585
-
586
- // Flush remaining content
587
- if (currentList.length > 0) {
588
- if (inList) {
589
- currentList.sort();
590
- }
591
- sortedLines.push(...currentList);
592
- }
593
-
594
- return sortedLines.join('\n');
595
- }
596
-
597
- /**
598
- * Normalize markdown link references for deterministic comparison
599
- */
600
- function normalizeMarkdownLinks(text) {
601
- // Extract and sort link references [id]: url "title"
602
- const linkRefPattern = /^\[([^\]]+)\]:\s*(.+)$/gm;
603
- const links = [];
604
- let match;
605
-
606
- while ((match = linkRefPattern.exec(text)) !== null) {
607
- links.push(match[0]);
608
- }
609
-
610
- if (links.length > 0) {
611
- // Sort links by ID
612
- links.sort();
613
- // Replace all link references with sorted version
614
- let normalized = text.replace(linkRefPattern, 'LINKREF');
615
- // Append sorted links at the end
616
- normalized = normalized.replace(/LINKREF/g, () => links.shift() || 'LINKREF');
617
- return normalized;
618
- }
619
-
620
- return text;
621
- }
622
-
623
- /**
624
- * Normalize "Defined in" links emitted by TypeDoc so that branch/remote differences
625
- * don't cause hash mismatches between environments.
626
- */
627
- function normalizeDefinedInLinks(text) {
628
- const definedInPattern = /\[(packages\/core\/[^\]]+?:\d+)\]\((https?:\/\/[^(]+?#L\d+)\)/g;
629
- return text.replace(definedInPattern, (_match, displayText) => displayText);
630
- }
631
-
632
- /**
633
- * Normalize content for comparison (remove trailing whitespace, normalize line endings, version numbers, timestamps)
634
- */
635
- function normalizeContent(content) {
636
- let normalized = content
637
- .replace(/\r\n/g, '\n') // Normalize line endings
638
- .replace(/\r/g, '\n') // Handle old Mac line endings
639
- .replace(/[ \t]+$/gm, '') // Remove trailing spaces/tabs
640
- .replace(/\n{3,}/g, '\n\n'); // Normalize multiple blank lines
641
-
642
- // Remove version numbers from TypeDoc-generated content
643
- // TypeDoc includes version like "@jmruthers/pace-core@0.5.158"
644
- // We normalize this to "@jmruthers/pace-core@VERSION" so version changes don't trigger updates
645
- // This allows docs to remain unchanged when only the version number changes
646
- normalized = normalized.replace(/@jmruthers\/pace-core@[\d.]+/g, '@jmruthers/pace-core@VERSION');
647
- normalized = normalized.replace(/version\s+[\d.]+/gi, 'version VERSION');
648
- // Also handle any other version patterns that might appear
649
- normalized = normalized.replace(/\b[\d]+\.[\d]+\.[\d]+/g, 'VERSION');
650
-
651
- // Remove timestamps and dates that might be generated dynamically
652
- normalized = normalized.replace(/\d{4}-\d{2}-\d{2}/g, 'DATE'); // YYYY-MM-DD
653
- normalized = normalized.replace(/\d{2}\/\d{2}\/\d{4}/g, 'DATE'); // MM/DD/YYYY
654
- normalized = normalized.replace(/\d{2}:\d{2}:\d{2}/g, 'TIME'); // HH:MM:SS
655
- normalized = normalized.replace(/Generated\s+on[^\n]*/gi, 'Generated on DATE');
656
- normalized = normalized.replace(/Last\s+updated[^\n]*/gi, 'Last updated DATE');
657
- normalized = normalized.replace(/Updated\s+[^\n]*/gi, 'Updated DATE');
658
-
659
- // Remove any git commit hashes or SHAs that might appear
660
- normalized = normalized.replace(/\b[0-9a-f]{7,40}\b/gi, 'HASH');
661
-
662
- // Remove any ISO timestamps
663
- normalized = normalized.replace(/\d{4}-\d{2}-\d{2}T\d{2}:\d{2}:\d{2}[.\d]*Z?/g, 'TIMESTAMP');
664
-
665
- // Normalize whitespace more aggressively
666
- normalized = normalized.replace(/[ \t]+/g, ' '); // Multiple spaces/tabs to single space
667
- normalized = normalized.replace(/ \n/g, '\n'); // Remove trailing spaces before newlines
668
-
669
- // Sort markdown lists for deterministic comparison
670
- normalized = sortMarkdownListItems(normalized);
671
-
672
- // Normalize markdown link references
673
- normalized = normalizeMarkdownLinks(normalized);
674
-
675
- // Normalize "Defined in" GitHub links
676
- normalized = normalizeDefinedInLinks(normalized);
677
-
678
- return normalized.trim();
679
- }
680
-
681
- /**
682
- * Get file content hash for comparison
683
- */
684
- function getFileHash(filePath) {
685
- if (!existsSync(filePath)) {
686
- return null;
687
- }
688
- try {
689
- const content = readFileSync(filePath, 'utf-8');
690
- const normalized = normalizeContent(content);
691
- return createHash('md5').update(normalized).digest('hex');
692
- } catch (err) {
693
- return null;
694
- }
695
- }
696
-
697
- /**
698
- * Get hashes of all existing doc files
699
- */
700
- function getExistingDocHashes() {
701
- const hashes = new Map();
702
- if (!existsSync(DOCS_DIR)) {
703
- return hashes;
704
- }
705
-
706
- const docFiles = scanDirectory(DOCS_DIR, ['.md']);
707
- for (const filePath of docFiles) {
708
- const relativePath = relative(DOCS_DIR, filePath);
709
- const hash = getFileHash(filePath);
710
- if (hash) {
711
- hashes.set(relativePath, hash);
712
- }
713
- }
714
-
715
- return hashes;
716
- }
717
-
718
- /**
719
- * Get git status of docs directory
720
- */
721
- function getGitStatus(directory) {
722
- try {
723
- const result = execSync(`git status --porcelain ${directory}`, {
724
- cwd: PACKAGE_ROOT,
725
- encoding: 'utf-8',
726
- stdio: 'pipe'
727
- });
728
- return result.trim().split('\n').filter(line => line.trim());
729
- } catch (err) {
730
- // Not a git repo or git not available
731
- return [];
732
- }
733
- }
734
-
735
- /**
736
- * Run TypeDoc to generate documentation, but only update files that actually changed
737
- */
738
- function runTypeDoc(dryRun = false) {
739
- if (dryRun) {
740
- console.log('🔍 DRY-RUN MODE: Showing what would be updated without actually updating');
741
- }
742
- console.log('🔨 Running TypeDoc to generate documentation...');
743
- console.log(`📝 Incremental build script v2.0 - Only updating files with actual content changes`);
744
-
745
- // Get hashes of existing files before generation
746
- // Version numbers are normalized out during hash calculation
747
- const existingHashes = getExistingDocHashes();
748
- const totalExistingFiles = existingHashes.size;
749
- console.log(`📋 Found ${totalExistingFiles} existing documentation files to compare against`);
750
-
751
- // Create temp directory for new docs
752
- const tempDocsDir = join(PACKAGE_ROOT, '.docs-temp');
753
-
754
- try {
755
- // Backup current docs if they exist
756
- const backupDir = join(PACKAGE_ROOT, '.docs-backup');
757
- if (existsSync(DOCS_DIR)) {
758
- if (existsSync(backupDir)) {
759
- rmSync(backupDir, { recursive: true, force: true });
760
- }
761
- cpSync(DOCS_DIR, backupDir, { recursive: true });
762
- }
763
-
764
- // Run TypeDoc to temp directory first
765
- const tempTypedocConfig = {
766
- ...JSON.parse(readFileSync(join(PACKAGE_ROOT, 'typedoc.json'), 'utf-8')),
767
- out: '.docs-temp'
768
- };
769
- const tempConfigPath = join(PACKAGE_ROOT, '.typedoc-temp.json');
770
- writeFileSync(tempConfigPath, JSON.stringify(tempTypedocConfig, null, 2));
771
-
772
- execSync(`npx typedoc --options ${tempConfigPath}`, {
773
- cwd: PACKAGE_ROOT,
774
- stdio: 'inherit'
775
- });
776
-
777
- // Compare and only copy changed files
778
- let updatedCount = 0;
779
- let unchangedCount = 0;
780
-
781
- if (existsSync(tempDocsDir)) {
782
- const newDocFiles = scanDirectory(tempDocsDir, ['.md']);
783
-
784
- // Ensure docs directory exists
785
- if (!existsSync(DOCS_DIR)) {
786
- mkdirSync(DOCS_DIR, { recursive: true });
787
- }
788
-
789
- const updatedFiles = [];
790
-
791
- for (const newFilePath of newDocFiles) {
792
- const relativePath = relative(tempDocsDir, newFilePath);
793
- const targetPath = join(DOCS_DIR, relativePath);
794
- const newHash = getFileHash(newFilePath);
795
- const oldHash = existingHashes.get(relativePath);
796
-
797
- // CRITICAL: Only copy if normalized content hash changed
798
- // If hash matches, do NOT write the file at all to prevent git from seeing it as modified
799
- if (newHash !== oldHash) {
800
- // Double-check: read existing file and compare normalized content
801
- if (existsSync(targetPath)) {
802
- const existingContent = readFileSync(targetPath, 'utf-8');
803
- const existingNormalized = normalizeContent(existingContent);
804
- const newContent = readFileSync(newFilePath, 'utf-8');
805
- const newNormalized = normalizeContent(newContent);
806
-
807
- // If normalized content is identical, skip write even if hash differs
808
- // (this handles edge cases where hash calculation might differ)
809
- if (existingNormalized === newNormalized) {
810
- unchangedCount++;
811
- continue;
812
- }
813
- }
814
-
815
- // Ensure directory exists
816
- const targetDir = dirname(targetPath);
817
- if (!existsSync(targetDir)) {
818
- mkdirSync(targetDir, { recursive: true });
819
- }
820
-
821
- // Copy file only if content actually changed
822
- if (!dryRun) {
823
- const content = readFileSync(newFilePath, 'utf-8');
824
- writeFileSync(targetPath, content, 'utf-8');
825
- }
826
-
827
- updatedCount++;
828
- updatedFiles.push(relativePath);
829
- } else {
830
- // Content is identical - DO NOT TOUCH THE FILE AT ALL
831
- // This prevents git from seeing it as modified
832
- // We don't restore timestamps or do anything - just leave it alone
833
- unchangedCount++;
834
- }
835
- }
836
-
837
- // Log which files were updated if any (only if debug mode or significant changes)
838
- if (updatedCount > 0) {
839
- if (process.env.DEBUG_DOCS || updatedCount <= 10) {
840
- console.log(`\n📝 Files updated (${updatedCount}):`);
841
- updatedFiles.slice(0, 10).forEach(file => console.log(` - ${file}`));
842
- if (updatedFiles.length > 10) {
843
- console.log(` ... and ${updatedFiles.length - 10} more`);
844
- }
845
- } else {
846
- console.log(`\n📝 ${updatedCount} files were updated (use DEBUG_DOCS=1 to see list)`);
847
- }
848
- }
849
-
850
- // Handle deleted files - files that existed before but don't exist in new generation
851
- // (TypeDoc might remove some files if exports changed)
852
- for (const [relativePath, oldHash] of existingHashes.entries()) {
853
- const targetPath = join(DOCS_DIR, relativePath);
854
- const newFilePath = join(tempDocsDir, relativePath);
855
- if (!existsSync(newFilePath) && existsSync(targetPath)) {
856
- // File was deleted by TypeDoc - remove it
857
- rmSync(targetPath, { force: true });
858
- console.log(` Deleted: ${relativePath}`);
859
- }
860
- }
861
-
862
- // Clean up temp files
863
- rmSync(tempDocsDir, { recursive: true, force: true });
864
- if (existsSync(backupDir)) {
865
- rmSync(backupDir, { recursive: true, force: true });
866
- }
867
- if (existsSync(tempConfigPath)) {
868
- rmSync(tempConfigPath, { force: true });
869
- }
870
-
871
- console.log(`📊 Documentation update summary:`);
872
- console.log(` Updated: ${updatedCount} files`);
873
- console.log(` Unchanged: ${unchangedCount} files`);
874
-
875
- // Verification step: Check if suspicious number of files changed
876
- if (updatedCount > 50 && updatedCount === newDocFiles.length) {
877
- console.log(`\n⚠️ WARNING: All ${updatedCount} files appear to have changed!`);
878
- console.log(` This might indicate TypeDoc is generating non-deterministic content.`);
879
- console.log(` Check if source files actually changed, or if TypeDoc config changed.`);
880
-
881
- // Additional verification: Check if source files actually changed
882
- const cache = loadCache();
883
- const currentSourceModTime = getLatestSourceModTime();
884
- if (cache.lastBuildTime > 0 && currentSourceModTime <= cache.sourceModTime) {
885
- console.log(`\n🔍 VERIFICATION: Source files have NOT changed since last build.`);
886
- console.log(` This suggests TypeDoc may be generating non-deterministic output.`);
887
- console.log(` Files were updated but source is unchanged - this is unexpected.`);
888
- }
889
- }
890
-
891
- // Verification: If many files changed but normalized content matches, warn
892
- if (updatedCount > 0 && unchangedCount > 0) {
893
- const changeRatio = updatedCount / (updatedCount + unchangedCount);
894
- if (changeRatio > 0.8) {
895
- console.log(`\n⚠️ WARNING: ${Math.round(changeRatio * 100)}% of files were updated.`);
896
- console.log(` This is unusually high. Verify source files actually changed.`);
897
- }
898
- }
899
-
900
- // Get final file hashes after update
901
- const finalHashes = getExistingDocHashes();
902
- const fileHashesObj = {};
903
- for (const [relativePath, hash] of finalHashes.entries()) {
904
- fileHashesObj[relativePath] = hash;
905
- }
906
-
907
- return { success: true, fileHashes: fileHashesObj };
908
- }
909
-
910
- return { success: true, fileHashes: {} };
911
- } catch (error) {
912
- // Clean up on error
913
- if (existsSync(tempDocsDir)) {
914
- rmSync(tempDocsDir, { recursive: true, force: true });
915
- }
916
- const tempConfigPath = join(PACKAGE_ROOT, '.typedoc-temp.json');
917
- if (existsSync(tempConfigPath)) {
918
- rmSync(tempConfigPath, { force: true });
919
- }
920
- console.error('❌ TypeDoc failed:', error.message);
921
- return { success: false, fileHashes: {} };
922
- }
923
- }
36
+ const IGNORE_PATTERNS = [
37
+ /\.test\.(ts|tsx)$/,
38
+ /\.spec\.(ts|tsx)$/,
39
+ /__tests__/,
40
+ /\/test\//,
41
+ /\/tests\//
42
+ ];
924
43
 
925
44
  /**
926
45
  * Main function
@@ -930,11 +49,41 @@ function main() {
930
49
  const dryRun = process.argv.includes('--dry-run');
931
50
  const verifyGit = process.argv.includes('--verify-git');
932
51
 
52
+ // Create configuration object for modules
53
+ const config = {
54
+ packageRoot: PACKAGE_ROOT,
55
+ repoRoot: REPO_ROOT,
56
+ srcDir: SRC_DIR,
57
+ docsDir: DOCS_DIR,
58
+ cacheFile: CACHE_FILE,
59
+ docsRelevantPaths: DOCS_RELEVANT_PATHS,
60
+ ignorePatterns: IGNORE_PATTERNS,
61
+ getLatestSourceModTime,
62
+ getLatestDocModTime,
63
+ getSourceFileHashes: (srcDir) => getSourceFileHashes(srcDir, IGNORE_PATTERNS),
64
+ getExistingDocHashes,
65
+ hasSourceChanged,
66
+ hashesMatch,
67
+ shouldSkipDueToCleanGitState,
68
+ primeCacheFromExistingDocs: (config) => primeCacheFromExistingDocs({
69
+ cacheFile: config.cacheFile,
70
+ docsDir: config.docsDir,
71
+ srcDir: config.srcDir,
72
+ getLatestSourceModTime: config.getLatestSourceModTime,
73
+ getLatestDocModTime: config.getLatestDocModTime,
74
+ getExistingDocHashes: config.getExistingDocHashes,
75
+ getSourceFileHashes: config.getSourceFileHashes
76
+ })
77
+ };
78
+
933
79
  if (forceRebuild) {
934
80
  console.log('🔄 Force rebuild requested, regenerating all documentation...');
935
- const result = runTypeDoc(dryRun);
81
+ const result = runTypeDoc({
82
+ ...config,
83
+ dryRun
84
+ });
936
85
  if (result.success) {
937
- const currentSourceHashes = getSourceFileHashes();
86
+ const currentSourceHashes = getSourceFileHashes(SRC_DIR, IGNORE_PATTERNS);
938
87
  const sourceHashesObj = {};
939
88
  for (const [relativePath, hash] of currentSourceHashes.entries()) {
940
89
  sourceHashesObj[relativePath] = hash;
@@ -942,13 +91,13 @@ function main() {
942
91
 
943
92
  const cache = {
944
93
  lastBuildTime: Date.now(),
945
- sourceModTime: getLatestSourceModTime(),
946
- docModTime: getLatestDocModTime(),
94
+ sourceModTime: getLatestSourceModTime(SRC_DIR, IGNORE_PATTERNS),
95
+ docModTime: getLatestDocModTime(DOCS_DIR),
947
96
  fileHashes: result.fileHashes || {},
948
97
  sourceFileHashes: sourceHashesObj
949
98
  };
950
99
  if (!dryRun) {
951
- saveCache(cache);
100
+ saveCache(CACHE_FILE, cache);
952
101
  }
953
102
  console.log('✅ Documentation generation completed');
954
103
  }
@@ -956,8 +105,16 @@ function main() {
956
105
  return;
957
106
  }
958
107
 
108
+ // Load cache for decision making
109
+ const cache = loadCache(CACHE_FILE);
110
+
959
111
  // Aggressive pre-check: Skip TypeDoc entirely if source unchanged
960
- if (!shouldRunTypeDoc()) {
112
+ const shouldRunConfig = {
113
+ ...config,
114
+ cache
115
+ };
116
+
117
+ if (!shouldRunTypeDoc(shouldRunConfig)) {
961
118
  console.log('⏭️ Skipping TypeDoc - source files unchanged and docs are up to date');
962
119
  process.exit(0);
963
120
  return;
@@ -966,14 +123,22 @@ function main() {
966
123
  // Get git status before if verifying
967
124
  let gitStatusBefore = [];
968
125
  if (verifyGit) {
969
- gitStatusBefore = getGitStatus(DOCS_DIR);
126
+ gitStatusBefore = getGitStatus(PACKAGE_ROOT, DOCS_DIR);
970
127
  console.log(`📋 Git status before: ${gitStatusBefore.length} modified files`);
971
128
  }
972
129
 
973
- if (needsRebuild()) {
974
- const result = runTypeDoc(dryRun);
130
+ const needsRebuildConfig = {
131
+ ...config,
132
+ cache
133
+ };
134
+
135
+ if (needsRebuild(needsRebuildConfig)) {
136
+ const result = runTypeDoc({
137
+ ...config,
138
+ dryRun
139
+ });
975
140
  if (result.success) {
976
- const currentSourceHashes = getSourceFileHashes();
141
+ const currentSourceHashes = getSourceFileHashes(SRC_DIR, IGNORE_PATTERNS);
977
142
  const sourceHashesObj = {};
978
143
  for (const [relativePath, hash] of currentSourceHashes.entries()) {
979
144
  sourceHashesObj[relativePath] = hash;
@@ -981,18 +146,18 @@ function main() {
981
146
 
982
147
  const cache = {
983
148
  lastBuildTime: Date.now(),
984
- sourceModTime: getLatestSourceModTime(),
985
- docModTime: getLatestDocModTime(),
149
+ sourceModTime: getLatestSourceModTime(SRC_DIR, IGNORE_PATTERNS),
150
+ docModTime: getLatestDocModTime(DOCS_DIR),
986
151
  fileHashes: result.fileHashes || {},
987
152
  sourceFileHashes: sourceHashesObj
988
153
  };
989
154
  if (!dryRun) {
990
- saveCache(cache);
155
+ saveCache(CACHE_FILE, cache);
991
156
  }
992
157
 
993
158
  // Verify git status after if requested
994
159
  if (verifyGit && !dryRun) {
995
- const gitStatusAfter = getGitStatus(DOCS_DIR);
160
+ const gitStatusAfter = getGitStatus(PACKAGE_ROOT, DOCS_DIR);
996
161
  const newChanges = gitStatusAfter.filter(file => !gitStatusBefore.includes(file));
997
162
  if (newChanges.length > 0 && result.updatedCount === 0) {
998
163
  console.log(`\n⚠️ WARNING: Git shows ${newChanges.length} files changed but script reported 0 updates`);
@@ -1012,4 +177,3 @@ function main() {
1012
177
  }
1013
178
 
1014
179
  main();
1015
-