@jmruthers/pace-core 0.5.193 → 0.6.2

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (577) hide show
  1. package/CHANGELOG.md +62 -0
  2. package/README.md +7 -1
  3. package/cursor-rules/00-pace-core-compliance.mdc +299 -0
  4. package/cursor-rules/01-standards-compliance.mdc +244 -0
  5. package/cursor-rules/02-project-structure.mdc +200 -0
  6. package/cursor-rules/03-solid-principles.mdc +222 -0
  7. package/cursor-rules/04-testing-standards.mdc +268 -0
  8. package/cursor-rules/05-bug-reports-and-features.mdc +246 -0
  9. package/cursor-rules/06-code-quality.mdc +309 -0
  10. package/cursor-rules/07-tech-stack-compliance.mdc +214 -0
  11. package/cursor-rules/08-markup-quality.mdc +452 -0
  12. package/cursor-rules/CHANGELOG.md +119 -0
  13. package/cursor-rules/README.md +192 -0
  14. package/dist/{AuthService-DjnJHDtC.d.ts → AuthService-BPvc3Ka0.d.ts} +54 -0
  15. package/dist/{DataTable-Be6dH_dR.d.ts → DataTable-BMRU8a1j.d.ts} +34 -2
  16. package/dist/{DataTable-5FU7IESH.js → DataTable-TPTKCX4D.js} +10 -9
  17. package/dist/{PublicPageProvider-C0Sm_e5k.d.ts → PublicPageProvider-DC6kCaqf.d.ts} +385 -261
  18. package/dist/{UnifiedAuthProvider-RGJTDE2C.js → UnifiedAuthProvider-CH6Z342H.js} +3 -3
  19. package/dist/{UnifiedAuthProvider-185Ih4dj.d.ts → UnifiedAuthProvider-CVcTjx-d.d.ts} +29 -0
  20. package/dist/{api-N774RPUA.js → api-MVVQZLJI.js} +2 -2
  21. package/dist/{chunk-KNC55RTG.js → chunk-24UVZUZG.js} +90 -54
  22. package/dist/chunk-24UVZUZG.js.map +1 -0
  23. package/dist/{chunk-HWIIPPNI.js → chunk-2UOI2FG5.js} +20 -20
  24. package/dist/chunk-2UOI2FG5.js.map +1 -0
  25. package/dist/{chunk-E3SPN4VZ 5.js → chunk-3XC4CPTD.js} +4345 -3986
  26. package/dist/chunk-3XC4CPTD.js.map +1 -0
  27. package/dist/{chunk-7EQTDTTJ.js → chunk-6J4GEEJR.js} +172 -45
  28. package/dist/chunk-6J4GEEJR.js.map +1 -0
  29. package/dist/{chunk-6C4YBBJM 5.js → chunk-6SOIHG6Z.js} +1 -1
  30. package/dist/chunk-6SOIHG6Z.js.map +1 -0
  31. package/dist/{chunk-7FLMSG37.js → chunk-EHMR7VYL.js} +25 -25
  32. package/dist/chunk-EHMR7VYL.js.map +1 -0
  33. package/dist/{chunk-I7PSE6JW.js → chunk-F2IMUDXZ.js} +2 -75
  34. package/dist/chunk-F2IMUDXZ.js.map +1 -0
  35. package/dist/{chunk-QWWZ5CAQ.js → chunk-FFQEQTNW.js} +7 -9
  36. package/dist/chunk-FFQEQTNW.js.map +1 -0
  37. package/dist/chunk-FMUCXFII.js +76 -0
  38. package/dist/chunk-FMUCXFII.js.map +1 -0
  39. package/dist/{chunk-HW3OVDUF.js → chunk-J36DSWQK.js} +1 -1
  40. package/dist/{chunk-HW3OVDUF.js.map → chunk-J36DSWQK.js.map} +1 -1
  41. package/dist/{chunk-SQGMNID3.js → chunk-L4OXEN46.js} +4 -5
  42. package/dist/chunk-L4OXEN46.js.map +1 -0
  43. package/dist/{chunk-R77UEZ4E 3.js → chunk-M43Y4SSO.js} +1 -1
  44. package/dist/chunk-M43Y4SSO.js.map +1 -0
  45. package/dist/{chunk-IIELH4DL.js → chunk-MMZ7JXPU.js} +60 -223
  46. package/dist/chunk-MMZ7JXPU.js.map +1 -0
  47. package/dist/{chunk-NOAYCWCX 5.js → chunk-NECFR5MM.js} +394 -312
  48. package/dist/chunk-NECFR5MM.js.map +1 -0
  49. package/dist/{chunk-BC4IJKSL.js → chunk-SFZUDBL5.js} +40 -4
  50. package/dist/chunk-SFZUDBL5.js.map +1 -0
  51. package/dist/{chunk-XNXXZ43G.js → chunk-XWQCNGTQ.js} +748 -364
  52. package/dist/chunk-XWQCNGTQ.js.map +1 -0
  53. package/dist/components.d.ts +6 -6
  54. package/dist/components.js +15 -12
  55. package/dist/components.js.map +1 -1
  56. package/dist/{functions-D_kgHktt.d.ts → functions-DHebl8-F.d.ts} +1 -1
  57. package/dist/hooks.d.ts +59 -126
  58. package/dist/hooks.js +19 -28
  59. package/dist/hooks.js.map +1 -1
  60. package/dist/index.d.ts +63 -16
  61. package/dist/index.js +23 -24
  62. package/dist/index.js.map +1 -1
  63. package/dist/providers.d.ts +21 -3
  64. package/dist/providers.js +2 -2
  65. package/dist/rbac/index.d.ts +146 -115
  66. package/dist/rbac/index.js +8 -11
  67. package/dist/theming/runtime.d.ts +1 -13
  68. package/dist/theming/runtime.js +1 -1
  69. package/dist/{timezone-_pgH8qrY.d.ts → timezone-CHhWg6b4.d.ts} +3 -10
  70. package/dist/{types-UU913iLA.d.ts → types-BeoeWV5I.d.ts} +8 -0
  71. package/dist/{types-CEpcvwwF.d.ts → types-CkbwOr4Y.d.ts} +6 -0
  72. package/dist/types.d.ts +2 -2
  73. package/dist/{usePublicRouteParams-TZe0gy-4.d.ts → usePublicRouteParams-1oMokgLF.d.ts} +34 -4
  74. package/dist/{useToast-C8gR5ir4.d.ts → useToast-AyaT-x7p.d.ts} +2 -2
  75. package/dist/utils.d.ts +4 -5
  76. package/dist/utils.js +15 -15
  77. package/dist/utils.js.map +1 -1
  78. package/docs/api/README.md +7 -1
  79. package/docs/api/classes/ColumnFactory.md +8 -8
  80. package/docs/api/classes/InvalidScopeError.md +4 -4
  81. package/docs/api/classes/Logger.md +1 -1
  82. package/docs/api/classes/MissingUserContextError.md +4 -4
  83. package/docs/api/classes/OrganisationContextRequiredError.md +4 -4
  84. package/docs/api/classes/PermissionDeniedError.md +4 -4
  85. package/docs/api/classes/RBACAuditManager.md +1 -1
  86. package/docs/api/classes/RBACCache.md +1 -1
  87. package/docs/api/classes/RBACEngine.md +1 -1
  88. package/docs/api/classes/RBACError.md +4 -4
  89. package/docs/api/classes/RBACNotInitializedError.md +4 -4
  90. package/docs/api/classes/SecureSupabaseClient.md +18 -15
  91. package/docs/api/classes/StorageUtils.md +1 -1
  92. package/docs/api/enums/FileCategory.md +1 -1
  93. package/docs/api/enums/LogLevel.md +1 -1
  94. package/docs/api/enums/RBACErrorCode.md +1 -1
  95. package/docs/api/enums/RPCFunction.md +1 -1
  96. package/docs/api/interfaces/AddressFieldProps.md +1 -1
  97. package/docs/api/interfaces/AddressFieldRef.md +1 -1
  98. package/docs/api/interfaces/AggregateConfig.md +4 -4
  99. package/docs/api/interfaces/AutocompleteOptions.md +1 -1
  100. package/docs/api/interfaces/AvatarProps.md +1 -1
  101. package/docs/api/interfaces/BadgeProps.md +9 -2
  102. package/docs/api/interfaces/ButtonProps.md +7 -4
  103. package/docs/api/interfaces/CalendarProps.md +8 -5
  104. package/docs/api/interfaces/CardProps.md +8 -5
  105. package/docs/api/interfaces/ColorPalette.md +1 -1
  106. package/docs/api/interfaces/ColorShade.md +1 -1
  107. package/docs/api/interfaces/ComplianceResult.md +1 -1
  108. package/docs/api/interfaces/DataAccessRecord.md +9 -9
  109. package/docs/api/interfaces/DataRecord.md +1 -1
  110. package/docs/api/interfaces/DataTableAction.md +24 -21
  111. package/docs/api/interfaces/DataTableColumn.md +31 -31
  112. package/docs/api/interfaces/DataTableProps.md +1 -1
  113. package/docs/api/interfaces/DataTableToolbarButton.md +7 -7
  114. package/docs/api/interfaces/DatabaseComplianceResult.md +1 -1
  115. package/docs/api/interfaces/DatabaseIssue.md +1 -1
  116. package/docs/api/interfaces/EmptyStateConfig.md +5 -5
  117. package/docs/api/interfaces/EnhancedNavigationMenuProps.md +1 -1
  118. package/docs/api/interfaces/ErrorBoundaryProps.md +147 -0
  119. package/docs/api/interfaces/ErrorBoundaryProviderProps.md +36 -0
  120. package/docs/api/interfaces/ErrorBoundaryState.md +75 -0
  121. package/docs/api/interfaces/EventAppRoleData.md +1 -1
  122. package/docs/api/interfaces/ExportColumn.md +1 -1
  123. package/docs/api/interfaces/ExportOptions.md +8 -8
  124. package/docs/api/interfaces/FileDisplayProps.md +1 -1
  125. package/docs/api/interfaces/FileMetadata.md +1 -1
  126. package/docs/api/interfaces/FileReference.md +1 -1
  127. package/docs/api/interfaces/FileSizeLimits.md +1 -1
  128. package/docs/api/interfaces/FileUploadOptions.md +1 -1
  129. package/docs/api/interfaces/FileUploadProps.md +26 -23
  130. package/docs/api/interfaces/FooterProps.md +10 -8
  131. package/docs/api/interfaces/FormFieldProps.md +10 -10
  132. package/docs/api/interfaces/FormProps.md +1 -1
  133. package/docs/api/interfaces/GrantEventAppRoleParams.md +1 -1
  134. package/docs/api/interfaces/InactivityWarningModalProps.md +1 -1
  135. package/docs/api/interfaces/InputProps.md +7 -4
  136. package/docs/api/interfaces/LabelProps.md +1 -1
  137. package/docs/api/interfaces/LoggerConfig.md +1 -1
  138. package/docs/api/interfaces/LoginFormProps.md +14 -11
  139. package/docs/api/interfaces/NavigationAccessRecord.md +1 -1
  140. package/docs/api/interfaces/NavigationContextType.md +1 -1
  141. package/docs/api/interfaces/NavigationGuardProps.md +1 -1
  142. package/docs/api/interfaces/NavigationItem.md +11 -11
  143. package/docs/api/interfaces/NavigationMenuProps.md +15 -15
  144. package/docs/api/interfaces/NavigationProviderProps.md +1 -1
  145. package/docs/api/interfaces/Organisation.md +1 -1
  146. package/docs/api/interfaces/OrganisationContextType.md +1 -1
  147. package/docs/api/interfaces/OrganisationMembership.md +1 -1
  148. package/docs/api/interfaces/OrganisationProviderProps.md +1 -1
  149. package/docs/api/interfaces/OrganisationSecurityError.md +1 -1
  150. package/docs/api/interfaces/PaceAppLayoutProps.md +30 -27
  151. package/docs/api/interfaces/PaceLoginPageProps.md +6 -4
  152. package/docs/api/interfaces/PageAccessRecord.md +1 -1
  153. package/docs/api/interfaces/PagePermissionContextType.md +1 -1
  154. package/docs/api/interfaces/PagePermissionGuardProps.md +1 -1
  155. package/docs/api/interfaces/PagePermissionProviderProps.md +1 -1
  156. package/docs/api/interfaces/PaletteData.md +1 -1
  157. package/docs/api/interfaces/ParsedAddress.md +1 -1
  158. package/docs/api/interfaces/PermissionEnforcerProps.md +1 -1
  159. package/docs/api/interfaces/ProgressProps.md +1 -1
  160. package/docs/api/interfaces/ProtectedRouteProps.md +7 -26
  161. package/docs/api/interfaces/PublicPageFooterProps.md +9 -9
  162. package/docs/api/interfaces/PublicPageHeaderProps.md +10 -10
  163. package/docs/api/interfaces/PublicPageLayoutProps.md +7 -20
  164. package/docs/api/interfaces/QuickFix.md +1 -1
  165. package/docs/api/interfaces/RBACAccessValidateParams.md +1 -1
  166. package/docs/api/interfaces/RBACAccessValidateResult.md +1 -1
  167. package/docs/api/interfaces/RBACAuditLogParams.md +1 -1
  168. package/docs/api/interfaces/RBACAuditLogResult.md +1 -1
  169. package/docs/api/interfaces/RBACConfig.md +1 -1
  170. package/docs/api/interfaces/RBACContext.md +1 -1
  171. package/docs/api/interfaces/RBACLogger.md +1 -1
  172. package/docs/api/interfaces/RBACPageAccessCheckParams.md +1 -1
  173. package/docs/api/interfaces/RBACPerformanceMetrics.md +1 -1
  174. package/docs/api/interfaces/RBACPermissionCheckParams.md +1 -1
  175. package/docs/api/interfaces/RBACPermissionCheckResult.md +1 -1
  176. package/docs/api/interfaces/RBACPermissionsGetParams.md +1 -1
  177. package/docs/api/interfaces/RBACPermissionsGetResult.md +1 -1
  178. package/docs/api/interfaces/RBACResult.md +1 -1
  179. package/docs/api/interfaces/RBACRoleGrantParams.md +1 -1
  180. package/docs/api/interfaces/RBACRoleGrantResult.md +1 -1
  181. package/docs/api/interfaces/RBACRoleRevokeParams.md +1 -1
  182. package/docs/api/interfaces/RBACRoleRevokeResult.md +1 -1
  183. package/docs/api/interfaces/RBACRoleValidateParams.md +1 -1
  184. package/docs/api/interfaces/RBACRoleValidateResult.md +1 -1
  185. package/docs/api/interfaces/RBACRolesListParams.md +1 -1
  186. package/docs/api/interfaces/RBACRolesListResult.md +1 -1
  187. package/docs/api/interfaces/RBACSessionTrackParams.md +1 -1
  188. package/docs/api/interfaces/RBACSessionTrackResult.md +1 -1
  189. package/docs/api/interfaces/ResourcePermissions.md +1 -1
  190. package/docs/api/interfaces/RevokeEventAppRoleParams.md +1 -1
  191. package/docs/api/interfaces/RoleBasedRouterContextType.md +1 -1
  192. package/docs/api/interfaces/RoleBasedRouterProps.md +1 -1
  193. package/docs/api/interfaces/RoleManagementResult.md +1 -1
  194. package/docs/api/interfaces/RouteAccessRecord.md +1 -1
  195. package/docs/api/interfaces/RouteConfig.md +1 -1
  196. package/docs/api/interfaces/RuntimeComplianceResult.md +1 -1
  197. package/docs/api/interfaces/SecureDataContextType.md +9 -9
  198. package/docs/api/interfaces/SecureDataProviderProps.md +8 -8
  199. package/docs/api/interfaces/SessionRestorationLoaderProps.md +3 -3
  200. package/docs/api/interfaces/SetupIssue.md +1 -1
  201. package/docs/api/interfaces/StorageConfig.md +1 -1
  202. package/docs/api/interfaces/StorageFileInfo.md +1 -1
  203. package/docs/api/interfaces/StorageFileMetadata.md +1 -1
  204. package/docs/api/interfaces/StorageListOptions.md +1 -1
  205. package/docs/api/interfaces/StorageListResult.md +1 -1
  206. package/docs/api/interfaces/StorageUploadOptions.md +1 -1
  207. package/docs/api/interfaces/StorageUploadResult.md +1 -1
  208. package/docs/api/interfaces/StorageUrlOptions.md +1 -1
  209. package/docs/api/interfaces/StyleImport.md +1 -1
  210. package/docs/api/interfaces/SwitchProps.md +1 -1
  211. package/docs/api/interfaces/TabsContentProps.md +1 -1
  212. package/docs/api/interfaces/TabsListProps.md +1 -1
  213. package/docs/api/interfaces/TabsProps.md +1 -1
  214. package/docs/api/interfaces/TabsTriggerProps.md +3 -3
  215. package/docs/api/interfaces/TextareaProps.md +1 -1
  216. package/docs/api/interfaces/ToastActionElement.md +4 -1
  217. package/docs/api/interfaces/ToastProps.md +1 -1
  218. package/docs/api/interfaces/UnifiedAuthContextType.md +58 -55
  219. package/docs/api/interfaces/UnifiedAuthProviderProps.md +15 -13
  220. package/docs/api/interfaces/UseFormDialogOptions.md +1 -1
  221. package/docs/api/interfaces/UseFormDialogReturn.md +1 -1
  222. package/docs/api/interfaces/UseInactivityTrackerOptions.md +11 -9
  223. package/docs/api/interfaces/UseInactivityTrackerReturn.md +8 -8
  224. package/docs/api/interfaces/UsePublicEventLogoOptions.md +6 -6
  225. package/docs/api/interfaces/UsePublicEventLogoReturn.md +9 -6
  226. package/docs/api/interfaces/UsePublicEventOptions.md +3 -3
  227. package/docs/api/interfaces/UsePublicEventReturn.md +8 -5
  228. package/docs/api/interfaces/UsePublicFileDisplayOptions.md +4 -4
  229. package/docs/api/interfaces/UsePublicFileDisplayReturn.md +12 -9
  230. package/docs/api/interfaces/UsePublicRouteParamsReturn.md +10 -7
  231. package/docs/api/interfaces/UseResolvedScopeOptions.md +1 -1
  232. package/docs/api/interfaces/UseResolvedScopeReturn.md +1 -1
  233. package/docs/api/interfaces/UseResourcePermissionsOptions.md +1 -1
  234. package/docs/api/interfaces/UserEventAccess.md +14 -11
  235. package/docs/api/interfaces/UserMenuProps.md +8 -6
  236. package/docs/api/interfaces/UserProfile.md +1 -1
  237. package/docs/api/modules.md +575 -634
  238. package/docs/architecture/database-schema-requirements.md +161 -0
  239. package/docs/core-concepts/rbac-system.md +3 -3
  240. package/docs/documentation-index.md +2 -4
  241. package/docs/getting-started/cursor-rules.md +263 -0
  242. package/docs/getting-started/installation-guide.md +6 -1
  243. package/docs/getting-started/quick-start.md +6 -1
  244. package/docs/migration/DOCUMENTATION_STRUCTURE.md +441 -0
  245. package/docs/migration/MIGRATION_GUIDE.md +6 -28
  246. package/docs/migration/README.md +52 -6
  247. package/docs/migration/V0.5.190_TO_V0.6.1_MIGRATION.md +1153 -0
  248. package/docs/migration/V0.6.0_REACT_19_MIGRATION.md +227 -0
  249. package/docs/migration/database-changes-december-2025.md +3 -3
  250. package/docs/rbac/event-based-apps.md +1 -1
  251. package/docs/rbac/getting-started.md +1 -1
  252. package/docs/rbac/quick-start.md +1 -1
  253. package/docs/standards/README.md +40 -0
  254. package/docs/troubleshooting/migration.md +4 -4
  255. package/examples/PublicPages/PublicEventPage.tsx +1 -1
  256. package/package.json +12 -6
  257. package/scripts/audit/core/checks/accessibility.cjs +197 -0
  258. package/scripts/audit/core/checks/api-usage.cjs +191 -0
  259. package/scripts/audit/core/checks/bundle.cjs +142 -0
  260. package/scripts/{check-pace-core-compliance.cjs → audit/core/checks/compliance.cjs} +737 -691
  261. package/scripts/audit/core/checks/config.cjs +54 -0
  262. package/scripts/audit/core/checks/coverage.cjs +84 -0
  263. package/scripts/audit/core/checks/dependencies.cjs +454 -0
  264. package/scripts/audit/core/checks/documentation.cjs +203 -0
  265. package/scripts/audit/core/checks/environment.cjs +128 -0
  266. package/scripts/audit/core/checks/error-handling.cjs +299 -0
  267. package/scripts/audit/core/checks/forms.cjs +172 -0
  268. package/scripts/audit/core/checks/heuristics.cjs +68 -0
  269. package/scripts/audit/core/checks/hooks.cjs +334 -0
  270. package/scripts/audit/core/checks/imports.cjs +244 -0
  271. package/scripts/audit/core/checks/performance.cjs +325 -0
  272. package/scripts/audit/core/checks/routes.cjs +117 -0
  273. package/scripts/audit/core/checks/state.cjs +130 -0
  274. package/scripts/audit/core/checks/structure.cjs +65 -0
  275. package/scripts/audit/core/checks/style.cjs +584 -0
  276. package/scripts/audit/core/checks/testing.cjs +122 -0
  277. package/scripts/audit/core/checks/typescript.cjs +61 -0
  278. package/scripts/audit/core/scanner.cjs +199 -0
  279. package/scripts/audit/core/utils.cjs +137 -0
  280. package/scripts/audit/index.cjs +223 -0
  281. package/scripts/audit/reporters/console.cjs +151 -0
  282. package/scripts/audit/reporters/json.cjs +54 -0
  283. package/scripts/audit/reporters/markdown.cjs +124 -0
  284. package/scripts/audit-consuming-app.cjs +86 -0
  285. package/scripts/build-docs/build-decision.js +240 -0
  286. package/scripts/build-docs/cache-utils.js +105 -0
  287. package/scripts/build-docs/content-normalization.js +150 -0
  288. package/scripts/build-docs/file-utils.js +105 -0
  289. package/scripts/build-docs/git-utils.js +86 -0
  290. package/scripts/build-docs/hash-utils.js +116 -0
  291. package/scripts/build-docs/typedoc-runner.js +220 -0
  292. package/scripts/build-docs-incremental.js +77 -913
  293. package/scripts/install-cursor-rules.cjs +236 -0
  294. package/scripts/utils/command-runner.js +16 -11
  295. package/scripts/validate-formats.js +61 -56
  296. package/scripts/validate-master.js +74 -69
  297. package/scripts/validate-pre-publish.js +70 -65
  298. package/src/__tests__/helpers/test-providers.tsx +1 -1
  299. package/src/__tests__/helpers/test-utils.tsx +1 -1
  300. package/src/__tests__/hooks/usePermissions.test.ts +2 -2
  301. package/src/components/Alert/Alert.test.tsx +12 -18
  302. package/src/components/Alert/Alert.tsx +5 -7
  303. package/src/components/Avatar/Avatar.test.tsx +4 -4
  304. package/src/components/Badge/Badge.tsx +16 -4
  305. package/src/components/Button/Button.tsx +27 -4
  306. package/src/components/Calendar/Calendar.tsx +9 -3
  307. package/src/components/Card/Card.tsx +4 -0
  308. package/src/components/Checkbox/Checkbox.test.tsx +12 -12
  309. package/src/components/Checkbox/Checkbox.tsx +2 -2
  310. package/src/components/DataTable/DataTable.test.tsx +57 -93
  311. package/src/components/DataTable/DataTable.tsx +40 -6
  312. package/src/components/DataTable/__tests__/DataTableCore.test-setup.ts +5 -6
  313. package/src/components/DataTable/__tests__/pagination.modes.test.tsx +29 -7
  314. package/src/components/DataTable/__tests__/ssr.strict-mode.test.tsx +12 -12
  315. package/src/components/DataTable/__tests__/test-utils/sharedTestUtils.tsx +2 -3
  316. package/src/components/DataTable/components/AccessDeniedPage.tsx +17 -26
  317. package/src/components/DataTable/components/ActionButtons.tsx +10 -7
  318. package/src/components/DataTable/components/BulkOperationsDropdown.tsx +2 -2
  319. package/src/components/DataTable/components/ColumnFilter.tsx +10 -0
  320. package/src/components/DataTable/components/ColumnVisibilityDropdown.tsx +12 -0
  321. package/src/components/DataTable/components/DataTableBody.tsx +8 -0
  322. package/src/components/DataTable/components/DataTableCore.tsx +200 -561
  323. package/src/components/DataTable/components/DataTableErrorBoundary.tsx +11 -0
  324. package/src/components/DataTable/components/DataTableLayout.tsx +559 -0
  325. package/src/components/DataTable/components/DataTableModals.tsx +9 -1
  326. package/src/components/DataTable/components/DataTableToolbar.tsx +8 -0
  327. package/src/components/DataTable/components/DraggableColumnHeader.tsx +12 -0
  328. package/src/components/DataTable/components/EditFields.tsx +307 -0
  329. package/src/components/DataTable/components/EditableRow.tsx +9 -1
  330. package/src/components/DataTable/components/EmptyState.tsx +10 -0
  331. package/src/components/DataTable/components/FilterRow.tsx +12 -0
  332. package/src/components/DataTable/components/GroupHeader.tsx +12 -0
  333. package/src/components/DataTable/components/GroupingDropdown.tsx +12 -0
  334. package/src/components/DataTable/components/ImportModal.tsx +7 -0
  335. package/src/components/DataTable/components/LoadingState.tsx +6 -0
  336. package/src/components/DataTable/components/PaginationControls.tsx +16 -1
  337. package/src/components/DataTable/components/RowComponent.tsx +391 -0
  338. package/src/components/DataTable/components/UnifiedTableBody.tsx +62 -852
  339. package/src/components/DataTable/components/VirtualizedDataTable.tsx +16 -4
  340. package/src/components/DataTable/components/__tests__/AccessDeniedPage.test.tsx +4 -2
  341. package/src/components/DataTable/components/__tests__/DataTableModals.test.tsx +23 -23
  342. package/src/components/DataTable/components/__tests__/EditableRow.test.tsx +11 -11
  343. package/src/components/DataTable/components/__tests__/ExpandButton.test.tsx +36 -36
  344. package/src/components/DataTable/components/__tests__/GroupHeader.test.tsx +27 -27
  345. package/src/components/DataTable/components/__tests__/ImportModal.test.tsx +39 -39
  346. package/src/components/DataTable/components/__tests__/UnifiedTableBody.test.tsx +33 -33
  347. package/src/components/DataTable/components/__tests__/ViewRowModal.test.tsx +29 -29
  348. package/src/components/DataTable/components/cellValueUtils.ts +40 -0
  349. package/src/components/DataTable/components/hooks/useImportModalFocus.ts +53 -0
  350. package/src/components/DataTable/components/hooks/usePermissionTracking.ts +126 -0
  351. package/src/components/DataTable/context/DataTableContext.tsx +50 -0
  352. package/src/components/DataTable/core/ColumnFactory.ts +31 -0
  353. package/src/components/DataTable/core/DataTableContext.tsx +32 -1
  354. package/src/components/DataTable/hooks/useColumnOrderPersistence.ts +10 -0
  355. package/src/components/DataTable/hooks/useColumnReordering.ts +14 -2
  356. package/src/components/DataTable/hooks/useColumnVisibilityPersistence.ts +10 -0
  357. package/src/components/DataTable/hooks/useDataTableDataPipeline.ts +16 -0
  358. package/src/components/DataTable/hooks/useDataTablePermissions.ts +124 -32
  359. package/src/components/DataTable/hooks/useDataTableState.ts +35 -1
  360. package/src/components/DataTable/hooks/useEffectiveColumnOrder.ts +12 -0
  361. package/src/components/DataTable/hooks/useKeyboardNavigation.ts +2 -2
  362. package/src/components/DataTable/hooks/useServerSideDataEffect.ts +11 -0
  363. package/src/components/DataTable/hooks/useTableColumns.ts +8 -0
  364. package/src/components/DataTable/hooks/useTableHandlers.ts +14 -0
  365. package/src/components/DataTable/styles.ts +6 -6
  366. package/src/components/DataTable/types.ts +6 -10
  367. package/src/components/DataTable/utils/a11yUtils.ts +7 -0
  368. package/src/components/DataTable/utils/debugTools.ts +18 -113
  369. package/src/components/DataTable/utils/errorHandling.ts +12 -0
  370. package/src/components/DataTable/utils/exportUtils.ts +9 -0
  371. package/src/components/DataTable/utils/flexibleImport.ts +12 -48
  372. package/src/components/DataTable/utils/paginationUtils.ts +8 -0
  373. package/src/components/DataTable/utils/performanceUtils.ts +5 -1
  374. package/src/components/DatePickerWithTimezone/DatePickerWithTimezone.test.tsx +8 -14
  375. package/src/components/Dialog/Dialog.tsx +8 -7
  376. package/src/components/ErrorBoundary/ErrorBoundary.test.tsx +180 -1
  377. package/src/components/ErrorBoundary/ErrorBoundary.tsx +46 -6
  378. package/src/components/ErrorBoundary/ErrorBoundaryContext.tsx +129 -0
  379. package/src/components/ErrorBoundary/index.ts +27 -2
  380. package/src/components/EventSelector/EventSelector.tsx +4 -1
  381. package/src/components/FileDisplay/FileDisplay.test.tsx +2 -2
  382. package/src/components/FileDisplay/FileDisplay.tsx +32 -18
  383. package/src/components/FileUpload/FileUpload.tsx +22 -2
  384. package/src/components/Footer/Footer.test.tsx +16 -16
  385. package/src/components/Footer/Footer.tsx +15 -12
  386. package/src/components/Form/Form.test.tsx +36 -15
  387. package/src/components/Form/Form.tsx +31 -26
  388. package/src/components/Header/Header.tsx +22 -11
  389. package/src/components/InactivityWarningModal/InactivityWarningModal.test.tsx +40 -40
  390. package/src/components/InactivityWarningModal/InactivityWarningModal.tsx +1 -1
  391. package/src/components/Input/Input.test.tsx +2 -2
  392. package/src/components/Input/Input.tsx +36 -34
  393. package/src/components/Label/Label.tsx +1 -1
  394. package/src/components/LoadingSpinner/LoadingSpinner.test.tsx +4 -4
  395. package/src/components/LoadingSpinner/LoadingSpinner.tsx +1 -1
  396. package/src/components/LoginForm/LoginForm.test.tsx +42 -42
  397. package/src/components/LoginForm/LoginForm.tsx +12 -8
  398. package/src/components/NavigationMenu/NavigationMenu.tsx +15 -514
  399. package/src/components/NavigationMenu/types.ts +56 -0
  400. package/src/components/NavigationMenu/useNavigationFiltering.ts +390 -0
  401. package/src/components/OrganisationSelector/OrganisationSelector.tsx +3 -0
  402. package/src/components/PaceAppLayout/PaceAppLayout.performance.test.tsx +1 -1
  403. package/src/components/PaceAppLayout/PaceAppLayout.test.tsx +54 -52
  404. package/src/components/PaceAppLayout/PaceAppLayout.tsx +33 -12
  405. package/src/components/PaceAppLayout/README.md +1 -1
  406. package/src/components/PaceAppLayout/test-setup.tsx +1 -2
  407. package/src/components/PaceLoginPage/PaceLoginPage.tsx +4 -1
  408. package/src/components/PasswordChange/PasswordChangeForm.test.tsx +33 -33
  409. package/src/components/PasswordChange/PasswordChangeForm.tsx +10 -1
  410. package/src/components/Progress/Progress.tsx +1 -1
  411. package/src/components/ProtectedRoute/ProtectedRoute.tsx +3 -9
  412. package/src/components/PublicLayout/PublicPageLayout.tsx +3 -6
  413. package/src/components/PublicLayout/PublicPageProvider.tsx +4 -0
  414. package/src/components/Select/Select.tsx +95 -438
  415. package/src/components/Select/context.ts +23 -0
  416. package/src/components/Select/hooks/useSelectEvents.ts +87 -0
  417. package/src/components/Select/hooks/useSelectSearch.ts +91 -0
  418. package/src/components/Select/hooks/useSelectState.ts +104 -0
  419. package/src/components/Select/index.ts +9 -1
  420. package/src/components/Select/types.ts +123 -0
  421. package/src/components/Select/utils/text.ts +26 -0
  422. package/src/components/SessionRestorationLoader/SessionRestorationLoader.tsx +5 -6
  423. package/src/components/Switch/Switch.tsx +4 -4
  424. package/src/components/Table/Table.tsx +1 -1
  425. package/src/components/Tabs/Tabs.tsx +1 -1
  426. package/src/components/Textarea/Textarea.tsx +27 -29
  427. package/src/components/Toast/Toast.tsx +5 -1
  428. package/src/components/Tooltip/Tooltip.tsx +3 -3
  429. package/src/components/UserMenu/UserMenu.test.tsx +24 -11
  430. package/src/components/UserMenu/UserMenu.tsx +22 -19
  431. package/src/components/index.ts +2 -2
  432. package/src/hooks/__tests__/hooks.integration.test.tsx +80 -55
  433. package/src/hooks/__tests__/index.unit.test.ts +2 -5
  434. package/src/hooks/__tests__/useStorage.unit.test.ts +36 -36
  435. package/src/hooks/index.ts +1 -2
  436. package/src/hooks/public/usePublicEvent.ts +5 -1
  437. package/src/hooks/public/usePublicEventLogo.ts +5 -1
  438. package/src/hooks/public/usePublicFileDisplay.ts +4 -0
  439. package/src/hooks/public/usePublicRouteParams.ts +5 -1
  440. package/src/hooks/services/useAuth.ts +32 -0
  441. package/src/hooks/services/useCurrentEvent.ts +6 -0
  442. package/src/hooks/services/useCurrentOrganisation.ts +6 -0
  443. package/src/hooks/useDataTableState.ts +8 -18
  444. package/src/hooks/useDebounce.ts +9 -0
  445. package/src/hooks/useEventTheme.ts +6 -0
  446. package/src/hooks/useFileDisplay.ts +4 -0
  447. package/src/hooks/useFileReference.ts +25 -7
  448. package/src/hooks/useFileUrl.ts +11 -1
  449. package/src/hooks/useFocusManagement.ts +16 -2
  450. package/src/hooks/useFocusTrap.ts +7 -4
  451. package/src/hooks/useFormDialog.ts +8 -7
  452. package/src/hooks/useInactivityTracker.ts +4 -1
  453. package/src/hooks/useKeyboardShortcuts.ts +4 -0
  454. package/src/hooks/useOrganisationPermissions.ts +4 -0
  455. package/src/hooks/useOrganisationSecurity.ts +4 -0
  456. package/src/hooks/usePerformanceMonitor.ts +4 -0
  457. package/src/hooks/usePermissionCache.ts +8 -1
  458. package/src/hooks/useQueryCache.ts +12 -1
  459. package/src/hooks/useSessionRestoration.ts +4 -0
  460. package/src/hooks/useStorage.ts +4 -0
  461. package/src/hooks/useToast.ts +3 -3
  462. package/src/index.ts +2 -1
  463. package/src/providers/__tests__/OrganisationProvider.test.tsx +115 -49
  464. package/src/providers/__tests__/ProviderLifecycle.test.tsx +21 -6
  465. package/src/providers/__tests__/UnifiedAuthProvider.test.tsx +10 -10
  466. package/src/providers/services/AuthServiceProvider.tsx +18 -0
  467. package/src/providers/services/EventServiceProvider.tsx +18 -0
  468. package/src/providers/services/InactivityServiceProvider.tsx +18 -0
  469. package/src/providers/services/OrganisationServiceProvider.tsx +18 -0
  470. package/src/providers/services/UnifiedAuthProvider.tsx +58 -22
  471. package/src/providers/services/__tests__/AuthServiceProvider.integration.test.tsx +33 -7
  472. package/src/rbac/README.md +1 -1
  473. package/src/rbac/__tests__/adapters.comprehensive.test.tsx +26 -26
  474. package/src/rbac/__tests__/scenarios.user-role.test.tsx +4 -5
  475. package/src/rbac/adapters.tsx +14 -5
  476. package/src/rbac/api.ts +100 -67
  477. package/src/rbac/components/EnhancedNavigationMenu.tsx +1 -1
  478. package/src/rbac/components/NavigationGuard.tsx +1 -1
  479. package/src/rbac/components/NavigationProvider.tsx +5 -2
  480. package/src/rbac/components/PagePermissionGuard.tsx +158 -18
  481. package/src/rbac/components/PagePermissionProvider.tsx +1 -1
  482. package/src/rbac/components/PermissionEnforcer.tsx +1 -1
  483. package/src/rbac/components/RoleBasedRouter.tsx +6 -2
  484. package/src/rbac/components/SecureDataProvider.test.tsx +84 -49
  485. package/src/rbac/components/SecureDataProvider.tsx +21 -6
  486. package/src/rbac/components/__tests__/PagePermissionGuard.race-condition.test.tsx +24 -14
  487. package/src/rbac/components/__tests__/PagePermissionGuard.test.tsx +7 -0
  488. package/src/rbac/components/__tests__/PagePermissionGuard.verification.test.tsx +14 -6
  489. package/src/rbac/components/__tests__/RoleBasedRouter.test.tsx +15 -4
  490. package/src/rbac/components/__tests__/SecureDataProvider.fixed.test.tsx +148 -24
  491. package/src/rbac/components/__tests__/SecureDataProvider.test.tsx +81 -15
  492. package/src/rbac/engine.ts +38 -14
  493. package/src/rbac/hooks/permissions/index.ts +7 -0
  494. package/src/rbac/hooks/permissions/useAccessLevel.ts +105 -0
  495. package/src/rbac/hooks/permissions/useCachedPermissions.ts +79 -0
  496. package/src/rbac/hooks/permissions/useCan.ts +347 -0
  497. package/src/rbac/hooks/permissions/useHasAllPermissions.ts +90 -0
  498. package/src/rbac/hooks/permissions/useHasAnyPermission.ts +90 -0
  499. package/src/rbac/hooks/permissions/useMultiplePermissions.ts +93 -0
  500. package/src/rbac/hooks/permissions/usePermissions.ts +253 -0
  501. package/src/rbac/hooks/useCan.test.ts +71 -64
  502. package/src/rbac/hooks/usePermissions.ts +14 -995
  503. package/src/rbac/hooks/useResourcePermissions.test.ts +54 -18
  504. package/src/rbac/hooks/useResourcePermissions.ts +14 -4
  505. package/src/rbac/hooks/useSecureSupabase.ts +33 -13
  506. package/src/rbac/permissions.ts +0 -30
  507. package/src/rbac/secureClient.ts +212 -61
  508. package/src/rbac/types.ts +8 -0
  509. package/src/theming/__tests__/parseEventColours.test.ts +6 -9
  510. package/src/theming/parseEventColours.ts +5 -19
  511. package/src/types/vitest-globals.d.ts +51 -26
  512. package/src/utils/__mocks__/supabaseMock.ts +1 -3
  513. package/src/utils/__tests__/formatting.unit.test.ts +4 -4
  514. package/src/utils/__tests__/index.unit.test.ts +2 -2
  515. package/src/utils/audit/audit.ts +0 -3
  516. package/src/utils/core/cn.ts +1 -1
  517. package/src/utils/file-reference/index.ts +53 -1
  518. package/src/utils/formatting/formatting.ts +8 -18
  519. package/src/utils/index.ts +0 -1
  520. package/src/utils/security/secureDataAccess.test.ts +31 -20
  521. package/src/utils/security/secureDataAccess.ts +4 -3
  522. package/dist/chunk-6C4YBBJM.js +0 -628
  523. package/dist/chunk-6C4YBBJM.js.map +0 -1
  524. package/dist/chunk-7D4SUZUM.js 2.map +0 -1
  525. package/dist/chunk-7EQTDTTJ.js 2.map +0 -1
  526. package/dist/chunk-7EQTDTTJ.js.map +0 -1
  527. package/dist/chunk-7FLMSG37.js 2.map +0 -1
  528. package/dist/chunk-7FLMSG37.js.map +0 -1
  529. package/dist/chunk-BC4IJKSL.js.map +0 -1
  530. package/dist/chunk-E3SPN4VZ.js +0 -12917
  531. package/dist/chunk-E3SPN4VZ.js.map +0 -1
  532. package/dist/chunk-E66EQZE6 5.js +0 -37
  533. package/dist/chunk-E66EQZE6.js 2.map +0 -1
  534. package/dist/chunk-HWIIPPNI.js.map +0 -1
  535. package/dist/chunk-I7PSE6JW 5.js +0 -191
  536. package/dist/chunk-I7PSE6JW.js 2.map +0 -1
  537. package/dist/chunk-I7PSE6JW.js.map +0 -1
  538. package/dist/chunk-IIELH4DL.js.map +0 -1
  539. package/dist/chunk-KNC55RTG.js 5.map +0 -1
  540. package/dist/chunk-KNC55RTG.js.map +0 -1
  541. package/dist/chunk-KQCRWDSA.js 5.map +0 -1
  542. package/dist/chunk-LFNCN2SP.js +0 -412
  543. package/dist/chunk-LFNCN2SP.js 2.map +0 -1
  544. package/dist/chunk-LFNCN2SP.js.map +0 -1
  545. package/dist/chunk-LMC26NLJ 2.js +0 -84
  546. package/dist/chunk-NOAYCWCX.js +0 -4993
  547. package/dist/chunk-NOAYCWCX.js.map +0 -1
  548. package/dist/chunk-QWWZ5CAQ.js 3.map +0 -1
  549. package/dist/chunk-QWWZ5CAQ.js.map +0 -1
  550. package/dist/chunk-QXHPKYJV 3.js +0 -113
  551. package/dist/chunk-R77UEZ4E.js +0 -68
  552. package/dist/chunk-R77UEZ4E.js.map +0 -1
  553. package/dist/chunk-SQGMNID3.js.map +0 -1
  554. package/dist/chunk-VBXEHIUJ.js 6.map +0 -1
  555. package/dist/chunk-XNXXZ43G.js.map +0 -1
  556. package/dist/chunk-ZSAAAMVR 6.js +0 -25
  557. package/dist/components.js 5.map +0 -1
  558. package/dist/styles/index 2.js +0 -12
  559. package/dist/styles/index.js 5.map +0 -1
  560. package/dist/theming/runtime 5.js +0 -19
  561. package/dist/theming/runtime.js 5.map +0 -1
  562. package/docs/api/classes/ErrorBoundary.md +0 -144
  563. package/docs/migration/quick-migration-guide.md +0 -356
  564. package/docs/migration/service-architecture.md +0 -281
  565. package/src/hooks/__tests__/useSecureDataAccess.unit.test.tsx +0 -680
  566. package/src/hooks/useSecureDataAccess.test.ts +0 -559
  567. package/src/hooks/useSecureDataAccess.ts +0 -666
  568. /package/dist/{DataTable-5FU7IESH.js.map → DataTable-TPTKCX4D.js.map} +0 -0
  569. /package/dist/{UnifiedAuthProvider-RGJTDE2C.js.map → UnifiedAuthProvider-CH6Z342H.js.map} +0 -0
  570. /package/dist/{api-N774RPUA.js.map → api-MVVQZLJI.js.map} +0 -0
  571. /package/docs/migration/{organisation-context-timing-fix.md → V0.3.44_organisation-context-timing-fix.md} +0 -0
  572. /package/docs/migration/{rbac-migration.md → V0.4.0_rbac-migration.md} +0 -0
  573. /package/docs/migration/{person-scoped-profiles-migration-guide.md → V0.5.190_person-scoped-profiles-migration-guide.md} +0 -0
  574. /package/examples/{rbac → RBAC}/CompleteRBACExample.tsx +0 -0
  575. /package/examples/{rbac → RBAC}/EventBasedApp.tsx +0 -0
  576. /package/examples/{rbac → RBAC}/PermissionExample.tsx +0 -0
  577. /package/examples/{rbac → RBAC}/index.ts +0 -0
@@ -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
-