@jmruthers/pace-core 0.5.191 → 0.6.1
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.
- package/CHANGELOG.md +29 -0
- package/README.md +7 -1
- package/cursor-rules/00-pace-core-compliance.mdc +372 -0
- package/cursor-rules/01-standards-compliance.mdc +275 -0
- package/cursor-rules/02-project-structure.mdc +200 -0
- package/cursor-rules/03-solid-principles.mdc +341 -0
- package/cursor-rules/04-testing-standards.mdc +315 -0
- package/cursor-rules/05-bug-reports-and-features.mdc +246 -0
- package/cursor-rules/06-code-quality.mdc +392 -0
- package/cursor-rules/07-tech-stack-compliance.mdc +309 -0
- package/cursor-rules/CHANGELOG.md +101 -0
- package/cursor-rules/README.md +191 -0
- package/dist/{AuthService-CbP_utw2.d.ts → AuthService-DjnJHDtC.d.ts} +1 -0
- package/dist/{DataTable-Be6dH_dR.d.ts → DataTable-CH1U5Tpy.d.ts} +1 -1
- package/dist/{DataTable-WKRZD47S.js → DataTable-DQ7RSOHE.js} +8 -7
- package/dist/{PublicPageProvider-ULXC_u6U.d.ts → PublicPageProvider-ce4xlHYA.d.ts} +37 -156
- package/dist/{UnifiedAuthProvider-BYA9qB-o.d.ts → UnifiedAuthProvider-185Ih4dj.d.ts} +2 -0
- package/dist/{UnifiedAuthProvider-FTSG5XH7.js → UnifiedAuthProvider-ATAP5UTR.js} +3 -3
- package/dist/{api-IHKALJZD.js → api-N774RPUA.js} +2 -2
- package/dist/{chunk-6C4YBBJM.js → chunk-3QRJFVBR.js} +1 -1
- package/dist/chunk-3QRJFVBR.js.map +1 -0
- package/dist/{chunk-OETXORNB.js → chunk-3XTALGJF.js} +211 -136
- package/dist/chunk-3XTALGJF.js.map +1 -0
- package/dist/{chunk-6TQDD426.js → chunk-4N5C5XZU.js} +47 -228
- package/dist/chunk-4N5C5XZU.js.map +1 -0
- package/dist/{chunk-LOMZXPSN.js → chunk-4ZC4GX36.js} +47 -74
- package/dist/chunk-4ZC4GX36.js.map +1 -0
- package/dist/{chunk-6LTQQAT6.js → chunk-BYFSK72L.js} +357 -158
- package/dist/chunk-BYFSK72L.js.map +1 -0
- package/dist/{chunk-XYXSXPUK.js → chunk-EXUD6RNJ.js} +50 -10
- package/dist/chunk-EXUD6RNJ.js.map +1 -0
- package/dist/{chunk-VKB2CO4Z.js → chunk-GLK6VM3F.js} +244 -249
- package/dist/chunk-GLK6VM3F.js.map +1 -0
- package/dist/{chunk-HW3OVDUF.js → chunk-J36DSWQK.js} +1 -1
- package/dist/{chunk-HW3OVDUF.js.map → chunk-J36DSWQK.js.map} +1 -1
- package/dist/{chunk-XNYQOL3Z.js → chunk-JBKQ3SAO.js} +9 -18
- package/dist/chunk-JBKQ3SAO.js.map +1 -0
- package/dist/{chunk-ROXMHMY2.js → chunk-KNC55RTG.js} +13 -3
- package/dist/{chunk-ROXMHMY2.js.map → chunk-KNC55RTG.js.map} +1 -1
- package/dist/{chunk-QWWZ5CAQ.js → chunk-LXQLPRQ2.js} +2 -2
- package/dist/{chunk-ULHIJK66.js → chunk-T33XF5ZC.js} +255 -140
- package/dist/chunk-T33XF5ZC.js.map +1 -0
- package/dist/{chunk-VRGWKHDB.js → chunk-XM25TVIE.js} +100 -33
- package/dist/chunk-XM25TVIE.js.map +1 -0
- package/dist/components.d.ts +4 -4
- package/dist/components.js +9 -9
- package/dist/hooks.d.ts +6 -6
- package/dist/hooks.js +20 -25
- package/dist/hooks.js.map +1 -1
- package/dist/index.d.ts +11 -11
- package/dist/index.js +18 -21
- package/dist/index.js.map +1 -1
- package/dist/providers.d.ts +3 -3
- package/dist/providers.js +2 -2
- package/dist/rbac/index.d.ts +2 -20
- package/dist/rbac/index.js +7 -9
- package/dist/{usePublicRouteParams-TZe0gy-4.d.ts → usePublicRouteParams-BJAlWfuJ.d.ts} +3 -3
- package/dist/{useToast-C8gR5ir4.d.ts → useToast-AyaT-x7p.d.ts} +2 -2
- package/dist/utils.d.ts +1 -1
- package/dist/utils.js +3 -3
- package/docs/api/classes/ColumnFactory.md +1 -1
- package/docs/api/classes/ErrorBoundary.md +1 -1
- package/docs/api/classes/InvalidScopeError.md +1 -1
- package/docs/api/classes/Logger.md +1 -1
- package/docs/api/classes/MissingUserContextError.md +1 -1
- package/docs/api/classes/OrganisationContextRequiredError.md +1 -1
- package/docs/api/classes/PermissionDeniedError.md +2 -2
- package/docs/api/classes/RBACAuditManager.md +2 -2
- package/docs/api/classes/RBACCache.md +1 -1
- package/docs/api/classes/RBACEngine.md +2 -2
- package/docs/api/classes/RBACError.md +1 -1
- package/docs/api/classes/RBACNotInitializedError.md +1 -1
- package/docs/api/classes/SecureSupabaseClient.md +10 -10
- package/docs/api/classes/StorageUtils.md +1 -1
- package/docs/api/enums/FileCategory.md +1 -1
- package/docs/api/enums/LogLevel.md +1 -1
- package/docs/api/enums/RBACErrorCode.md +1 -1
- package/docs/api/enums/RPCFunction.md +1 -1
- package/docs/api/interfaces/AddressFieldProps.md +1 -1
- package/docs/api/interfaces/AddressFieldRef.md +1 -1
- package/docs/api/interfaces/AggregateConfig.md +1 -1
- package/docs/api/interfaces/AutocompleteOptions.md +1 -1
- package/docs/api/interfaces/AvatarProps.md +1 -1
- package/docs/api/interfaces/BadgeProps.md +1 -1
- package/docs/api/interfaces/ButtonProps.md +1 -1
- package/docs/api/interfaces/CalendarProps.md +1 -1
- package/docs/api/interfaces/CardProps.md +1 -1
- package/docs/api/interfaces/ColorPalette.md +1 -1
- package/docs/api/interfaces/ColorShade.md +1 -1
- package/docs/api/interfaces/ComplianceResult.md +1 -1
- package/docs/api/interfaces/DataAccessRecord.md +1 -1
- package/docs/api/interfaces/DataRecord.md +1 -1
- package/docs/api/interfaces/DataTableAction.md +1 -1
- package/docs/api/interfaces/DataTableColumn.md +1 -1
- package/docs/api/interfaces/DataTableProps.md +1 -1
- package/docs/api/interfaces/DataTableToolbarButton.md +1 -1
- package/docs/api/interfaces/DatabaseComplianceResult.md +1 -1
- package/docs/api/interfaces/DatabaseIssue.md +1 -1
- package/docs/api/interfaces/EmptyStateConfig.md +1 -1
- package/docs/api/interfaces/EnhancedNavigationMenuProps.md +1 -1
- package/docs/api/interfaces/EventAppRoleData.md +1 -1
- package/docs/api/interfaces/ExportColumn.md +1 -1
- package/docs/api/interfaces/ExportOptions.md +1 -1
- package/docs/api/interfaces/FileDisplayProps.md +24 -11
- package/docs/api/interfaces/FileMetadata.md +1 -1
- package/docs/api/interfaces/FileReference.md +1 -1
- package/docs/api/interfaces/FileSizeLimits.md +1 -1
- package/docs/api/interfaces/FileUploadOptions.md +1 -1
- package/docs/api/interfaces/FileUploadProps.md +1 -1
- package/docs/api/interfaces/FooterProps.md +1 -1
- package/docs/api/interfaces/FormFieldProps.md +1 -1
- package/docs/api/interfaces/FormProps.md +1 -1
- package/docs/api/interfaces/GrantEventAppRoleParams.md +1 -1
- package/docs/api/interfaces/InactivityWarningModalProps.md +1 -1
- package/docs/api/interfaces/InputProps.md +1 -1
- package/docs/api/interfaces/LabelProps.md +1 -1
- package/docs/api/interfaces/LoggerConfig.md +1 -1
- package/docs/api/interfaces/LoginFormProps.md +1 -1
- package/docs/api/interfaces/NavigationAccessRecord.md +2 -2
- package/docs/api/interfaces/NavigationContextType.md +1 -1
- package/docs/api/interfaces/NavigationGuardProps.md +1 -1
- package/docs/api/interfaces/NavigationItem.md +1 -1
- package/docs/api/interfaces/NavigationMenuProps.md +1 -1
- package/docs/api/interfaces/NavigationProviderProps.md +1 -1
- package/docs/api/interfaces/Organisation.md +1 -1
- package/docs/api/interfaces/OrganisationContextType.md +1 -1
- package/docs/api/interfaces/OrganisationMembership.md +1 -1
- package/docs/api/interfaces/OrganisationProviderProps.md +1 -1
- package/docs/api/interfaces/OrganisationSecurityError.md +1 -1
- package/docs/api/interfaces/PaceAppLayoutProps.md +1 -1
- package/docs/api/interfaces/PaceLoginPageProps.md +1 -1
- package/docs/api/interfaces/PageAccessRecord.md +1 -1
- package/docs/api/interfaces/PagePermissionContextType.md +1 -1
- package/docs/api/interfaces/PagePermissionGuardProps.md +2 -2
- package/docs/api/interfaces/PagePermissionProviderProps.md +1 -1
- package/docs/api/interfaces/PaletteData.md +1 -1
- package/docs/api/interfaces/ParsedAddress.md +1 -1
- package/docs/api/interfaces/PermissionEnforcerProps.md +4 -4
- package/docs/api/interfaces/ProgressProps.md +1 -1
- package/docs/api/interfaces/ProtectedRouteProps.md +1 -1
- package/docs/api/interfaces/PublicPageFooterProps.md +1 -1
- package/docs/api/interfaces/PublicPageHeaderProps.md +1 -1
- package/docs/api/interfaces/PublicPageLayoutProps.md +1 -1
- package/docs/api/interfaces/QuickFix.md +1 -1
- package/docs/api/interfaces/RBACAccessValidateParams.md +1 -1
- package/docs/api/interfaces/RBACAccessValidateResult.md +1 -1
- package/docs/api/interfaces/RBACAuditLogParams.md +1 -1
- package/docs/api/interfaces/RBACAuditLogResult.md +1 -1
- package/docs/api/interfaces/RBACConfig.md +2 -2
- package/docs/api/interfaces/RBACContext.md +1 -1
- package/docs/api/interfaces/RBACLogger.md +1 -1
- package/docs/api/interfaces/RBACPageAccessCheckParams.md +1 -1
- package/docs/api/interfaces/RBACPerformanceMetrics.md +1 -1
- package/docs/api/interfaces/RBACPermissionCheckParams.md +1 -1
- package/docs/api/interfaces/RBACPermissionCheckResult.md +2 -2
- package/docs/api/interfaces/RBACPermissionsGetParams.md +1 -1
- package/docs/api/interfaces/RBACPermissionsGetResult.md +1 -1
- package/docs/api/interfaces/RBACResult.md +1 -1
- package/docs/api/interfaces/RBACRoleGrantParams.md +2 -2
- package/docs/api/interfaces/RBACRoleGrantResult.md +1 -1
- package/docs/api/interfaces/RBACRoleRevokeParams.md +2 -2
- package/docs/api/interfaces/RBACRoleRevokeResult.md +1 -1
- package/docs/api/interfaces/RBACRoleValidateParams.md +2 -2
- package/docs/api/interfaces/RBACRoleValidateResult.md +1 -1
- package/docs/api/interfaces/RBACRolesListParams.md +1 -1
- package/docs/api/interfaces/RBACRolesListResult.md +2 -2
- package/docs/api/interfaces/RBACSessionTrackParams.md +1 -1
- package/docs/api/interfaces/RBACSessionTrackResult.md +1 -1
- package/docs/api/interfaces/ResourcePermissions.md +1 -1
- package/docs/api/interfaces/RevokeEventAppRoleParams.md +1 -1
- package/docs/api/interfaces/RoleBasedRouterContextType.md +1 -1
- package/docs/api/interfaces/RoleBasedRouterProps.md +1 -1
- package/docs/api/interfaces/RoleManagementResult.md +1 -1
- package/docs/api/interfaces/RouteAccessRecord.md +2 -2
- package/docs/api/interfaces/RouteConfig.md +2 -2
- package/docs/api/interfaces/RuntimeComplianceResult.md +1 -1
- package/docs/api/interfaces/SecureDataContextType.md +1 -1
- package/docs/api/interfaces/SecureDataProviderProps.md +1 -1
- package/docs/api/interfaces/SessionRestorationLoaderProps.md +1 -1
- package/docs/api/interfaces/SetupIssue.md +1 -1
- package/docs/api/interfaces/StorageConfig.md +1 -1
- package/docs/api/interfaces/StorageFileInfo.md +1 -1
- package/docs/api/interfaces/StorageFileMetadata.md +1 -1
- package/docs/api/interfaces/StorageListOptions.md +1 -1
- package/docs/api/interfaces/StorageListResult.md +1 -1
- package/docs/api/interfaces/StorageUploadOptions.md +1 -1
- package/docs/api/interfaces/StorageUploadResult.md +1 -1
- package/docs/api/interfaces/StorageUrlOptions.md +1 -1
- package/docs/api/interfaces/StyleImport.md +1 -1
- package/docs/api/interfaces/SwitchProps.md +1 -1
- package/docs/api/interfaces/TabsContentProps.md +1 -1
- package/docs/api/interfaces/TabsListProps.md +1 -1
- package/docs/api/interfaces/TabsProps.md +1 -1
- package/docs/api/interfaces/TabsTriggerProps.md +1 -1
- package/docs/api/interfaces/TextareaProps.md +1 -1
- package/docs/api/interfaces/ToastActionElement.md +1 -1
- package/docs/api/interfaces/ToastProps.md +1 -1
- package/docs/api/interfaces/UnifiedAuthContextType.md +60 -38
- package/docs/api/interfaces/UnifiedAuthProviderProps.md +13 -13
- package/docs/api/interfaces/UseFormDialogOptions.md +1 -1
- package/docs/api/interfaces/UseFormDialogReturn.md +1 -1
- package/docs/api/interfaces/UseInactivityTrackerOptions.md +1 -1
- package/docs/api/interfaces/UseInactivityTrackerReturn.md +1 -1
- package/docs/api/interfaces/UsePublicEventLogoOptions.md +2 -2
- package/docs/api/interfaces/UsePublicEventLogoReturn.md +1 -1
- package/docs/api/interfaces/UsePublicEventOptions.md +1 -1
- package/docs/api/interfaces/UsePublicEventReturn.md +1 -1
- package/docs/api/interfaces/UsePublicFileDisplayOptions.md +2 -2
- package/docs/api/interfaces/UsePublicFileDisplayReturn.md +1 -1
- package/docs/api/interfaces/UsePublicRouteParamsReturn.md +1 -1
- package/docs/api/interfaces/UseResolvedScopeOptions.md +2 -2
- package/docs/api/interfaces/UseResolvedScopeReturn.md +1 -1
- package/docs/api/interfaces/UseResourcePermissionsOptions.md +1 -1
- package/docs/api/interfaces/UserEventAccess.md +1 -1
- package/docs/api/interfaces/UserMenuProps.md +1 -1
- package/docs/api/interfaces/UserProfile.md +1 -1
- package/docs/api/modules.md +194 -209
- package/docs/getting-started/cursor-rules.md +262 -0
- package/docs/getting-started/installation-guide.md +6 -1
- package/docs/getting-started/quick-start.md +6 -1
- package/docs/migration/MIGRATION_GUIDE.md +4 -4
- package/docs/migration/REACT_19_MIGRATION.md +227 -0
- package/docs/migration/database-changes-december-2025.md +2 -1
- package/docs/rbac/event-based-apps.md +124 -6
- package/docs/standards/README.md +39 -0
- package/docs/troubleshooting/migration.md +4 -4
- package/examples/PublicPages/PublicEventPage.tsx +1 -1
- package/package.json +11 -6
- package/scripts/audit-consuming-app.cjs +961 -0
- package/scripts/check-pace-core-compliance.cjs +315 -61
- package/scripts/install-cursor-rules.cjs +236 -0
- package/src/__tests__/helpers/test-providers.tsx +1 -1
- package/src/__tests__/helpers/test-utils.tsx +1 -1
- package/src/__tests__/rls-policies.test.ts +3 -1
- package/src/components/Badge/Badge.tsx +2 -4
- package/src/components/Button/Button.tsx +5 -4
- package/src/components/Calendar/Calendar.tsx +1 -1
- package/src/components/DataTable/DataTable.test.tsx +57 -93
- package/src/components/DataTable/DataTable.tsx +2 -2
- package/src/components/DataTable/__tests__/DataTable.default-state.test.tsx +172 -45
- package/src/components/DataTable/__tests__/DataTable.grouping-aggregation.test.tsx +121 -28
- package/src/components/DataTable/__tests__/DataTableCore.test-setup.ts +9 -8
- package/src/components/DataTable/__tests__/DataTableCore.test.tsx +20 -52
- package/src/components/DataTable/__tests__/a11y.basic.test.tsx +170 -34
- package/src/components/DataTable/__tests__/keyboard.test.tsx +75 -12
- package/src/components/DataTable/__tests__/pagination.modes.test.tsx +88 -16
- package/src/components/DataTable/__tests__/ssr.strict-mode.test.tsx +12 -12
- package/src/components/DataTable/components/AccessDeniedPage.tsx +1 -1
- package/src/components/DataTable/components/BulkOperationsDropdown.tsx +1 -1
- package/src/components/DataTable/components/DataTableCore.tsx +4 -7
- package/src/components/DataTable/components/DataTableModals.tsx +1 -1
- package/src/components/DataTable/components/EditableRow.tsx +1 -1
- package/src/components/DataTable/components/UnifiedTableBody.tsx +86 -17
- package/src/components/DataTable/components/__tests__/DataTableModals.test.tsx +23 -23
- package/src/components/DataTable/components/__tests__/EditableRow.test.tsx +11 -11
- package/src/components/DataTable/components/__tests__/ExpandButton.test.tsx +36 -36
- package/src/components/DataTable/components/__tests__/GroupHeader.test.tsx +27 -27
- package/src/components/DataTable/components/__tests__/ImportModal.test.tsx +39 -39
- package/src/components/DataTable/components/__tests__/UnifiedTableBody.test.tsx +33 -33
- package/src/components/DataTable/components/__tests__/ViewRowModal.test.tsx +29 -29
- package/src/components/DataTable/hooks/useColumnReordering.ts +2 -2
- package/src/components/DataTable/hooks/useDataTablePermissions.ts +75 -10
- package/src/components/DataTable/hooks/useKeyboardNavigation.ts +2 -2
- package/src/components/DatePickerWithTimezone/DatePickerWithTimezone.test.tsx +8 -14
- package/src/components/Dialog/Dialog.tsx +6 -5
- package/src/components/ErrorBoundary/ErrorBoundary.tsx +1 -1
- package/src/components/EventSelector/EventSelector.tsx +1 -1
- package/src/components/FileDisplay/FileDisplay.test.tsx +4 -3
- package/src/components/FileDisplay/FileDisplay.tsx +16 -4
- package/src/components/Footer/Footer.tsx +1 -1
- package/src/components/Form/Form.test.tsx +36 -15
- package/src/components/Form/Form.tsx +30 -26
- package/src/components/Header/Header.tsx +1 -1
- package/src/components/InactivityWarningModal/InactivityWarningModal.test.tsx +40 -40
- package/src/components/InactivityWarningModal/InactivityWarningModal.tsx +1 -1
- package/src/components/Input/Input.tsx +28 -30
- package/src/components/Label/Label.tsx +1 -1
- package/src/components/LoadingSpinner/LoadingSpinner.tsx +1 -1
- package/src/components/LoginForm/LoginForm.test.tsx +42 -42
- package/src/components/LoginForm/LoginForm.tsx +8 -8
- package/src/components/NavigationMenu/NavigationMenu.test.tsx +6 -4
- package/src/components/NavigationMenu/NavigationMenu.tsx +2 -11
- package/src/components/OrganisationSelector/OrganisationSelector.tsx +0 -1
- package/src/components/PaceAppLayout/PaceAppLayout.performance.test.tsx +1 -1
- package/src/components/PaceAppLayout/PaceAppLayout.test.tsx +75 -52
- package/src/components/PaceAppLayout/PaceAppLayout.tsx +98 -69
- package/src/components/PaceAppLayout/README.md +1 -1
- package/src/components/PaceLoginPage/PaceLoginPage.tsx +1 -8
- package/src/components/PasswordChange/PasswordChangeForm.test.tsx +33 -33
- package/src/components/PasswordChange/PasswordChangeForm.tsx +1 -1
- package/src/components/Progress/Progress.tsx +1 -1
- package/src/components/ProtectedRoute/ProtectedRoute.test.tsx +5 -9
- package/src/components/ProtectedRoute/ProtectedRoute.tsx +0 -1
- package/src/components/PublicLayout/PublicPageLayout.tsx +1 -1
- package/src/components/PublicLayout/PublicPageProvider.tsx +0 -1
- package/src/components/Select/Select.tsx +33 -22
- package/src/components/SessionRestorationLoader/SessionRestorationLoader.tsx +1 -1
- package/src/components/Table/Table.tsx +1 -1
- package/src/components/Textarea/Textarea.tsx +27 -29
- package/src/components/Toast/Toast.tsx +1 -1
- package/src/components/Tooltip/Tooltip.tsx +1 -1
- package/src/components/UserMenu/UserMenu.tsx +1 -1
- package/src/hooks/__tests__/hooks.integration.test.tsx +80 -55
- package/src/hooks/__tests__/useSecureDataAccess.unit.test.tsx +14 -7
- package/src/hooks/__tests__/useStorage.unit.test.ts +36 -36
- package/src/hooks/public/usePublicEvent.ts +1 -1
- package/src/hooks/public/usePublicEventLogo.ts +1 -1
- package/src/hooks/public/usePublicRouteParams.ts +1 -1
- package/src/hooks/services/useAuthService.ts +21 -3
- package/src/hooks/services/useEventService.ts +21 -3
- package/src/hooks/services/useInactivityService.ts +21 -3
- package/src/hooks/services/useOrganisationService.ts +21 -3
- package/src/hooks/useDataTableState.ts +8 -18
- package/src/hooks/useFileDisplay.ts +10 -17
- package/src/hooks/useFocusManagement.ts +2 -2
- package/src/hooks/useFocusTrap.ts +4 -4
- package/src/hooks/useFormDialog.ts +8 -7
- package/src/hooks/useInactivityTracker.ts +1 -1
- package/src/hooks/usePermissionCache.ts +1 -1
- package/src/hooks/useSecureDataAccess.test.ts +16 -9
- package/src/hooks/useSecureDataAccess.ts +22 -6
- package/src/hooks/useToast.ts +2 -2
- package/src/providers/__tests__/OrganisationProvider.test.tsx +57 -13
- package/src/providers/__tests__/ProviderLifecycle.test.tsx +21 -6
- package/src/providers/__tests__/UnifiedAuthProvider.test.tsx +10 -10
- package/src/providers/services/EventServiceProvider.tsx +0 -8
- package/src/providers/services/UnifiedAuthProvider.tsx +196 -46
- package/src/providers/services/__tests__/AuthServiceProvider.integration.test.tsx +13 -3
- package/src/rbac/__tests__/adapters.comprehensive.test.tsx +34 -40
- package/src/rbac/__tests__/isSuperAdmin.real.test.ts +82 -0
- package/src/rbac/adapters.tsx +3 -22
- package/src/rbac/api.test.ts +2 -2
- package/src/rbac/api.ts +7 -1
- package/src/rbac/components/EnhancedNavigationMenu.tsx +3 -16
- package/src/rbac/components/NavigationGuard.tsx +2 -11
- package/src/rbac/components/NavigationProvider.tsx +1 -2
- package/src/rbac/components/PagePermissionGuard.tsx +1 -1
- package/src/rbac/components/PagePermissionProvider.tsx +1 -1
- package/src/rbac/components/PermissionEnforcer.tsx +46 -13
- package/src/rbac/components/RoleBasedRouter.tsx +1 -1
- package/src/rbac/components/SecureDataProvider.tsx +1 -2
- package/src/rbac/components/__tests__/EnhancedNavigationMenu.test.tsx +7 -43
- package/src/rbac/components/__tests__/NavigationGuard.test.tsx +4 -11
- package/src/rbac/components/__tests__/NavigationProvider.test.tsx +3 -3
- package/src/rbac/components/__tests__/SecureDataProvider.fixed.test.tsx +1 -1
- package/src/rbac/components/__tests__/SecureDataProvider.test.tsx +1 -1
- package/src/rbac/engine.ts +14 -2
- package/src/rbac/hooks/index.ts +0 -3
- package/src/rbac/hooks/usePermissions.ts +51 -11
- package/src/rbac/hooks/useRBAC.ts +3 -13
- package/src/rbac/hooks/useResolvedScope.test.ts +75 -54
- package/src/rbac/hooks/useResolvedScope.ts +58 -33
- package/src/rbac/hooks/useSecureSupabase.ts +4 -9
- package/src/rbac/secureClient.ts +43 -0
- package/src/services/EventService.ts +4 -57
- package/src/services/InactivityService.ts +127 -34
- package/src/services/OrganisationService.ts +68 -10
- package/src/utils/security/secureDataAccess.test.ts +31 -20
- package/src/utils/security/secureDataAccess.ts +4 -3
- package/dist/chunk-6C4YBBJM.js.map +0 -1
- package/dist/chunk-6LTQQAT6.js.map +0 -1
- package/dist/chunk-6TQDD426.js.map +0 -1
- package/dist/chunk-LOMZXPSN.js.map +0 -1
- package/dist/chunk-OETXORNB.js.map +0 -1
- package/dist/chunk-ULHIJK66.js.map +0 -1
- package/dist/chunk-VKB2CO4Z.js.map +0 -1
- package/dist/chunk-VRGWKHDB.js.map +0 -1
- package/dist/chunk-XNYQOL3Z.js.map +0 -1
- package/dist/chunk-XYXSXPUK.js.map +0 -1
- package/scripts/check-pace-core-compliance.js +0 -512
- package/src/rbac/hooks/useSuperAdminBypass.ts +0 -126
- package/src/utils/context/superAdminOverride.ts +0 -58
- /package/dist/{DataTable-WKRZD47S.js.map → DataTable-DQ7RSOHE.js.map} +0 -0
- /package/dist/{UnifiedAuthProvider-FTSG5XH7.js.map → UnifiedAuthProvider-ATAP5UTR.js.map} +0 -0
- /package/dist/{api-IHKALJZD.js.map → api-N774RPUA.js.map} +0 -0
- /package/dist/{chunk-QWWZ5CAQ.js.map → chunk-LXQLPRQ2.js.map} +0 -0
- /package/examples/{rbac → RBAC}/CompleteRBACExample.tsx +0 -0
- /package/examples/{rbac → RBAC}/EventBasedApp.tsx +0 -0
- /package/examples/{rbac → RBAC}/PermissionExample.tsx +0 -0
- /package/examples/{rbac → RBAC}/index.ts +0 -0
|
@@ -0,0 +1,961 @@
|
|
|
1
|
+
#!/usr/bin/env node
|
|
2
|
+
|
|
3
|
+
/**
|
|
4
|
+
* Comprehensive Audit Tool for Consuming Apps
|
|
5
|
+
* @package @jmruthers/pace-core
|
|
6
|
+
* @module Scripts/audit-consuming-app
|
|
7
|
+
*
|
|
8
|
+
* Performs comprehensive audit of consuming app including:
|
|
9
|
+
* - Phase 1: Deterministic checks (file structure, configs, coverage)
|
|
10
|
+
* - Phase 2: Heuristic checks (SOLID principles, code complexity)
|
|
11
|
+
* - Standards compliance
|
|
12
|
+
* - Testing compliance
|
|
13
|
+
* - Code quality metrics
|
|
14
|
+
*
|
|
15
|
+
* Generates timestamped markdown report in audit/ directory.
|
|
16
|
+
*/
|
|
17
|
+
|
|
18
|
+
const fs = require('fs');
|
|
19
|
+
const path = require('path');
|
|
20
|
+
|
|
21
|
+
// Import existing compliance checker
|
|
22
|
+
let complianceChecker;
|
|
23
|
+
try {
|
|
24
|
+
complianceChecker = require('./check-pace-core-compliance.cjs');
|
|
25
|
+
} catch (error) {
|
|
26
|
+
console.error('Error loading compliance checker:', error.message);
|
|
27
|
+
process.exit(1);
|
|
28
|
+
}
|
|
29
|
+
|
|
30
|
+
// Run compliance check and get results
|
|
31
|
+
function runComplianceCheck(projectRoot) {
|
|
32
|
+
const manifest = complianceChecker.loadManifest();
|
|
33
|
+
const files = complianceChecker.findSourceFiles(projectRoot, []);
|
|
34
|
+
|
|
35
|
+
console.log(` Scanning ${files.length} files for compliance issues...`);
|
|
36
|
+
|
|
37
|
+
// Scan all files
|
|
38
|
+
const allViolations = {
|
|
39
|
+
restrictedImports: [],
|
|
40
|
+
duplicateComponents: [],
|
|
41
|
+
duplicateHooks: [],
|
|
42
|
+
duplicateUtils: [],
|
|
43
|
+
suggestions: [],
|
|
44
|
+
customAuthCode: [],
|
|
45
|
+
duplicateConfig: [],
|
|
46
|
+
unprotectedPages: [],
|
|
47
|
+
directSupabaseAuth: [],
|
|
48
|
+
providerSetupIssues: [],
|
|
49
|
+
viteConfigIssues: [],
|
|
50
|
+
routerSetupIssues: [],
|
|
51
|
+
unnecessaryWrappers: [],
|
|
52
|
+
appDiscoveryIssues: []
|
|
53
|
+
};
|
|
54
|
+
|
|
55
|
+
let scanned = 0;
|
|
56
|
+
files.forEach(file => {
|
|
57
|
+
try {
|
|
58
|
+
const violations = complianceChecker.scanFile(file, manifest);
|
|
59
|
+
allViolations.restrictedImports.push(...violations.restrictedImports);
|
|
60
|
+
allViolations.duplicateComponents.push(...violations.duplicateComponents);
|
|
61
|
+
allViolations.duplicateHooks.push(...violations.duplicateHooks);
|
|
62
|
+
allViolations.duplicateUtils.push(...violations.duplicateUtils);
|
|
63
|
+
allViolations.suggestions.push(...violations.suggestions);
|
|
64
|
+
allViolations.customAuthCode.push(...violations.customAuthCode);
|
|
65
|
+
allViolations.duplicateConfig.push(...violations.duplicateConfig);
|
|
66
|
+
allViolations.unprotectedPages.push(...violations.unprotectedPages);
|
|
67
|
+
allViolations.directSupabaseAuth.push(...violations.directSupabaseAuth);
|
|
68
|
+
allViolations.providerSetupIssues.push(...violations.providerSetupIssues);
|
|
69
|
+
allViolations.viteConfigIssues.push(...violations.viteConfigIssues);
|
|
70
|
+
allViolations.routerSetupIssues.push(...violations.routerSetupIssues);
|
|
71
|
+
allViolations.unnecessaryWrappers.push(...violations.unnecessaryWrappers);
|
|
72
|
+
allViolations.appDiscoveryIssues.push(...violations.appDiscoveryIssues);
|
|
73
|
+
scanned++;
|
|
74
|
+
if (scanned % 50 === 0) {
|
|
75
|
+
process.stdout.write(` Scanned ${scanned}/${files.length} files...\r`);
|
|
76
|
+
}
|
|
77
|
+
} catch (error) {
|
|
78
|
+
// Skip files with errors
|
|
79
|
+
scanned++;
|
|
80
|
+
}
|
|
81
|
+
});
|
|
82
|
+
|
|
83
|
+
if (scanned > 0) {
|
|
84
|
+
process.stdout.write(` Scanned ${scanned}/${files.length} files...\n`);
|
|
85
|
+
}
|
|
86
|
+
|
|
87
|
+
return allViolations;
|
|
88
|
+
}
|
|
89
|
+
|
|
90
|
+
// Convert compliance violations to markdown
|
|
91
|
+
function complianceViolationsToMarkdown(allViolations) {
|
|
92
|
+
let markdown = '';
|
|
93
|
+
|
|
94
|
+
const totalRestricted = allViolations.restrictedImports.length;
|
|
95
|
+
const totalDuplicates =
|
|
96
|
+
allViolations.duplicateComponents.length +
|
|
97
|
+
allViolations.duplicateHooks.length +
|
|
98
|
+
allViolations.duplicateUtils.length;
|
|
99
|
+
const totalSuggestions = allViolations.suggestions.length;
|
|
100
|
+
const totalRbacAuth =
|
|
101
|
+
allViolations.customAuthCode.length +
|
|
102
|
+
allViolations.duplicateConfig.length +
|
|
103
|
+
allViolations.unprotectedPages.length +
|
|
104
|
+
allViolations.directSupabaseAuth.length;
|
|
105
|
+
const totalSetupIssues =
|
|
106
|
+
allViolations.providerSetupIssues.length +
|
|
107
|
+
allViolations.viteConfigIssues.length +
|
|
108
|
+
allViolations.routerSetupIssues.length;
|
|
109
|
+
const totalUnnecessaryWrappers = allViolations.unnecessaryWrappers.length;
|
|
110
|
+
const totalAppDiscovery = allViolations.appDiscoveryIssues.length;
|
|
111
|
+
const totalIssues = totalRestricted + totalDuplicates + totalSuggestions + totalRbacAuth + totalSetupIssues + totalUnnecessaryWrappers + totalAppDiscovery;
|
|
112
|
+
|
|
113
|
+
markdown += `### Summary\n\n`;
|
|
114
|
+
markdown += `- **Total Issues:** ${totalIssues}\n`;
|
|
115
|
+
markdown += `- **Restricted Imports:** ${totalRestricted}\n`;
|
|
116
|
+
markdown += `- **Duplicate Components/Hooks/Utils:** ${totalDuplicates}\n`;
|
|
117
|
+
markdown += `- **RBAC/Auth Issues:** ${totalRbacAuth}\n`;
|
|
118
|
+
markdown += `- **Setup Issues:** ${totalSetupIssues}\n`;
|
|
119
|
+
markdown += `- **Unnecessary Wrappers:** ${totalUnnecessaryWrappers}\n`;
|
|
120
|
+
markdown += `- **App Discovery Issues:** ${totalAppDiscovery}\n`;
|
|
121
|
+
markdown += `- **Suggestions:** ${totalSuggestions}\n\n`;
|
|
122
|
+
|
|
123
|
+
// Restricted Imports
|
|
124
|
+
if (totalRestricted > 0) {
|
|
125
|
+
markdown += `#### ❌ Restricted Imports (${totalRestricted})\n\n`;
|
|
126
|
+
allViolations.restrictedImports.forEach(({ module, reason, file, line }) => {
|
|
127
|
+
markdown += `- **${file}:${line}**\n`;
|
|
128
|
+
markdown += ` - Import: \`${module}\`\n`;
|
|
129
|
+
markdown += ` - Reason: ${reason}\n\n`;
|
|
130
|
+
});
|
|
131
|
+
} else {
|
|
132
|
+
markdown += `#### ✅ No Restricted Imports\n\n`;
|
|
133
|
+
}
|
|
134
|
+
|
|
135
|
+
// Duplicate Components
|
|
136
|
+
if (allViolations.duplicateComponents.length > 0) {
|
|
137
|
+
markdown += `#### ❌ Duplicate Components (${allViolations.duplicateComponents.length})\n\n`;
|
|
138
|
+
allViolations.duplicateComponents.forEach(({ component, file }) => {
|
|
139
|
+
markdown += `- **${file}**\n`;
|
|
140
|
+
markdown += ` - Component \`${component}\` conflicts with pace-core component\n`;
|
|
141
|
+
markdown += ` - Suggestion: Use \`${component}\` from '@jmruthers/pace-core' instead\n\n`;
|
|
142
|
+
});
|
|
143
|
+
}
|
|
144
|
+
|
|
145
|
+
// Duplicate Hooks
|
|
146
|
+
if (allViolations.duplicateHooks.length > 0) {
|
|
147
|
+
markdown += `#### ❌ Duplicate Hooks (${allViolations.duplicateHooks.length})\n\n`;
|
|
148
|
+
allViolations.duplicateHooks.forEach(({ hook, file }) => {
|
|
149
|
+
markdown += `- **${file}**\n`;
|
|
150
|
+
markdown += ` - Hook \`${hook}\` conflicts with pace-core hook\n`;
|
|
151
|
+
markdown += ` - Suggestion: Use \`${hook}\` from '@jmruthers/pace-core' instead\n\n`;
|
|
152
|
+
});
|
|
153
|
+
}
|
|
154
|
+
|
|
155
|
+
// Duplicate Utils
|
|
156
|
+
if (allViolations.duplicateUtils.length > 0) {
|
|
157
|
+
markdown += `#### ❌ Duplicate Utils (${allViolations.duplicateUtils.length})\n\n`;
|
|
158
|
+
allViolations.duplicateUtils.forEach(({ util, file }) => {
|
|
159
|
+
markdown += `- **${file}**\n`;
|
|
160
|
+
markdown += ` - Util \`${util}\` conflicts with pace-core util\n`;
|
|
161
|
+
markdown += ` - Suggestion: Use \`${util}\` from '@jmruthers/pace-core' instead\n\n`;
|
|
162
|
+
});
|
|
163
|
+
}
|
|
164
|
+
|
|
165
|
+
// Unnecessary Wrappers
|
|
166
|
+
if (totalUnnecessaryWrappers > 0) {
|
|
167
|
+
markdown += `#### ⚠️ Unnecessary Wrappers (${totalUnnecessaryWrappers})\n\n`;
|
|
168
|
+
allViolations.unnecessaryWrappers.forEach(({ component, wrappedComponent, file, line, reason, recommendation }) => {
|
|
169
|
+
markdown += `- **${file}:${line}**\n`;
|
|
170
|
+
markdown += ` - Component: \`${component}\`\n`;
|
|
171
|
+
markdown += ` - Wraps: \`${wrappedComponent}\`\n`;
|
|
172
|
+
markdown += ` - Issue: ${reason}\n`;
|
|
173
|
+
if (recommendation) {
|
|
174
|
+
markdown += ` - Recommendation: ${recommendation}\n\n`;
|
|
175
|
+
} else {
|
|
176
|
+
markdown += ` - Recommendation: Remove the wrapper and use the component directly.\n\n`;
|
|
177
|
+
}
|
|
178
|
+
});
|
|
179
|
+
}
|
|
180
|
+
|
|
181
|
+
// Custom Auth/RBAC Code
|
|
182
|
+
if (allViolations.customAuthCode.length > 0) {
|
|
183
|
+
// Separate by severity
|
|
184
|
+
const errors = allViolations.customAuthCode.filter(v => !v.severity || v.severity === 'error');
|
|
185
|
+
const warnings = allViolations.customAuthCode.filter(v => v.severity === 'warning');
|
|
186
|
+
|
|
187
|
+
if (errors.length > 0) {
|
|
188
|
+
markdown += `#### ❌ Custom Auth/RBAC Code (${errors.length})\n\n`;
|
|
189
|
+
errors.forEach(({ name, type, file, line, reason, replacement, example }) => {
|
|
190
|
+
markdown += `- **${file}:${line}**\n`;
|
|
191
|
+
markdown += ` - ${type}: \`${name}\`\n`;
|
|
192
|
+
markdown += ` - Reason: ${reason}\n`;
|
|
193
|
+
if (replacement) {
|
|
194
|
+
markdown += ` - Fix: ${replacement}\n`;
|
|
195
|
+
}
|
|
196
|
+
if (example) {
|
|
197
|
+
markdown += ` - Example:\n\`\`\`typescript\n${example}\n\`\`\`\n`;
|
|
198
|
+
}
|
|
199
|
+
markdown += `\n`;
|
|
200
|
+
});
|
|
201
|
+
}
|
|
202
|
+
|
|
203
|
+
if (warnings.length > 0) {
|
|
204
|
+
markdown += `#### ⚠️ Custom Auth/RBAC Warnings (${warnings.length})\n\n`;
|
|
205
|
+
warnings.forEach(({ name, type, file, line, reason, replacement, example }) => {
|
|
206
|
+
markdown += `- **${file}:${line}**\n`;
|
|
207
|
+
markdown += ` - ${type}: \`${name}\`\n`;
|
|
208
|
+
markdown += ` - Note: ${reason}\n`;
|
|
209
|
+
if (replacement) {
|
|
210
|
+
markdown += ` - Recommendation: ${replacement}\n`;
|
|
211
|
+
}
|
|
212
|
+
if (example) {
|
|
213
|
+
markdown += ` - Example:\n\`\`\`typescript\n${example}\n\`\`\`\n`;
|
|
214
|
+
}
|
|
215
|
+
markdown += `\n`;
|
|
216
|
+
});
|
|
217
|
+
}
|
|
218
|
+
}
|
|
219
|
+
|
|
220
|
+
// Duplicate Configurations
|
|
221
|
+
if (allViolations.duplicateConfig.length > 0) {
|
|
222
|
+
markdown += `#### ❌ Duplicate Configurations (${allViolations.duplicateConfig.length})\n\n`;
|
|
223
|
+
allViolations.duplicateConfig.forEach(({ type, file, count, reason }) => {
|
|
224
|
+
markdown += `- **${file}**\n`;
|
|
225
|
+
markdown += ` - Type: \`${type}\`${count ? ` (${count} instances)` : ''}\n`;
|
|
226
|
+
markdown += ` - Reason: ${reason}\n\n`;
|
|
227
|
+
});
|
|
228
|
+
}
|
|
229
|
+
|
|
230
|
+
// Provider Setup Issues
|
|
231
|
+
if (allViolations.providerSetupIssues.length > 0) {
|
|
232
|
+
markdown += `#### ❌ Provider Setup Issues (${allViolations.providerSetupIssues.length})\n\n`;
|
|
233
|
+
allViolations.providerSetupIssues.forEach(({ issue, file, line, recommendation }) => {
|
|
234
|
+
markdown += `- **${file}:${line}**\n`;
|
|
235
|
+
markdown += ` - Issue: ${issue}\n`;
|
|
236
|
+
if (recommendation) {
|
|
237
|
+
markdown += ` - Recommendation: ${recommendation}\n\n`;
|
|
238
|
+
}
|
|
239
|
+
});
|
|
240
|
+
}
|
|
241
|
+
|
|
242
|
+
// Vite Config Issues
|
|
243
|
+
if (allViolations.viteConfigIssues.length > 0) {
|
|
244
|
+
markdown += `#### ❌ Vite Config Issues (${allViolations.viteConfigIssues.length})\n\n`;
|
|
245
|
+
allViolations.viteConfigIssues.forEach(({ issue, file, line, recommendation }) => {
|
|
246
|
+
markdown += `- **${file}:${line}**\n`;
|
|
247
|
+
markdown += ` - Issue: ${issue}\n`;
|
|
248
|
+
if (recommendation) {
|
|
249
|
+
markdown += ` - Recommendation: ${recommendation}\n\n`;
|
|
250
|
+
}
|
|
251
|
+
});
|
|
252
|
+
}
|
|
253
|
+
|
|
254
|
+
// Router Setup Issues
|
|
255
|
+
if (allViolations.routerSetupIssues.length > 0) {
|
|
256
|
+
markdown += `#### ❌ Router Setup Issues (${allViolations.routerSetupIssues.length})\n\n`;
|
|
257
|
+
allViolations.routerSetupIssues.forEach(({ issue, file, line, recommendation }) => {
|
|
258
|
+
markdown += `- **${file}:${line}**\n`;
|
|
259
|
+
markdown += ` - Issue: ${issue}\n`;
|
|
260
|
+
if (recommendation) {
|
|
261
|
+
markdown += ` - Recommendation: ${recommendation}\n\n`;
|
|
262
|
+
}
|
|
263
|
+
});
|
|
264
|
+
}
|
|
265
|
+
|
|
266
|
+
// Unprotected Pages
|
|
267
|
+
if (allViolations.unprotectedPages.length > 0) {
|
|
268
|
+
markdown += `#### ❌ Unprotected Pages (${allViolations.unprotectedPages.length})\n\n`;
|
|
269
|
+
allViolations.unprotectedPages.forEach(({ page, file, recommendation }) => {
|
|
270
|
+
markdown += `- **${file}**\n`;
|
|
271
|
+
markdown += ` - Page: ${page}\n`;
|
|
272
|
+
if (recommendation) {
|
|
273
|
+
markdown += ` - Recommendation: ${recommendation}\n\n`;
|
|
274
|
+
}
|
|
275
|
+
});
|
|
276
|
+
}
|
|
277
|
+
|
|
278
|
+
// Direct Supabase Auth
|
|
279
|
+
if (allViolations.directSupabaseAuth.length > 0) {
|
|
280
|
+
markdown += `#### ❌ Direct Supabase Auth Usage (${allViolations.directSupabaseAuth.length})\n\n`;
|
|
281
|
+
allViolations.directSupabaseAuth.forEach(({ file, line, recommendation }) => {
|
|
282
|
+
markdown += `- **${file}:${line}**\n`;
|
|
283
|
+
if (recommendation) {
|
|
284
|
+
markdown += ` - Recommendation: ${recommendation}\n\n`;
|
|
285
|
+
}
|
|
286
|
+
});
|
|
287
|
+
}
|
|
288
|
+
|
|
289
|
+
// App Discovery Issues
|
|
290
|
+
if (totalAppDiscovery > 0) {
|
|
291
|
+
markdown += `#### ⚠️ App Discovery Issues (${totalAppDiscovery})\n\n`;
|
|
292
|
+
allViolations.appDiscoveryIssues.forEach(({ issue, file, recommendation }) => {
|
|
293
|
+
markdown += `- **${file}**\n`;
|
|
294
|
+
markdown += ` - Issue: ${issue}\n`;
|
|
295
|
+
if (recommendation) {
|
|
296
|
+
markdown += ` - Recommendation: ${recommendation}\n\n`;
|
|
297
|
+
}
|
|
298
|
+
});
|
|
299
|
+
}
|
|
300
|
+
|
|
301
|
+
// Suggestions
|
|
302
|
+
if (totalSuggestions > 0) {
|
|
303
|
+
markdown += `#### 💡 Suggestions (${totalSuggestions})\n\n`;
|
|
304
|
+
const grouped = {};
|
|
305
|
+
allViolations.suggestions.forEach(s => {
|
|
306
|
+
if (!grouped[s.file]) grouped[s.file] = [];
|
|
307
|
+
grouped[s.file].push(s);
|
|
308
|
+
});
|
|
309
|
+
Object.entries(grouped).forEach(([file, suggestions]) => {
|
|
310
|
+
markdown += `- **${file}**\n`;
|
|
311
|
+
suggestions.forEach(s => {
|
|
312
|
+
markdown += ` - ${s.suggestion}\n`;
|
|
313
|
+
});
|
|
314
|
+
markdown += `\n`;
|
|
315
|
+
});
|
|
316
|
+
}
|
|
317
|
+
|
|
318
|
+
if (totalIssues === 0) {
|
|
319
|
+
markdown += `✅ **Excellent!** No compliance issues found.\n\n`;
|
|
320
|
+
}
|
|
321
|
+
|
|
322
|
+
return markdown;
|
|
323
|
+
}
|
|
324
|
+
|
|
325
|
+
// ANSI color codes
|
|
326
|
+
const colors = {
|
|
327
|
+
reset: '\x1b[0m',
|
|
328
|
+
green: '\x1b[32m',
|
|
329
|
+
yellow: '\x1b[33m',
|
|
330
|
+
blue: '\x1b[34m',
|
|
331
|
+
cyan: '\x1b[36m',
|
|
332
|
+
bold: '\x1b[1m',
|
|
333
|
+
red: '\x1b[31m'
|
|
334
|
+
};
|
|
335
|
+
|
|
336
|
+
// Find project root
|
|
337
|
+
function findProjectRoot() {
|
|
338
|
+
let current = path.resolve(process.cwd());
|
|
339
|
+
while (current !== path.dirname(current)) {
|
|
340
|
+
if (fs.existsSync(path.join(current, 'package.json'))) {
|
|
341
|
+
return current;
|
|
342
|
+
}
|
|
343
|
+
current = path.dirname(current);
|
|
344
|
+
}
|
|
345
|
+
return process.cwd();
|
|
346
|
+
}
|
|
347
|
+
|
|
348
|
+
// Get package.json info
|
|
349
|
+
function getPackageInfo(projectRoot) {
|
|
350
|
+
const packagePath = path.join(projectRoot, 'package.json');
|
|
351
|
+
if (!fs.existsSync(packagePath)) {
|
|
352
|
+
return null;
|
|
353
|
+
}
|
|
354
|
+
try {
|
|
355
|
+
return JSON.parse(fs.readFileSync(packagePath, 'utf8'));
|
|
356
|
+
} catch (error) {
|
|
357
|
+
return null;
|
|
358
|
+
}
|
|
359
|
+
}
|
|
360
|
+
|
|
361
|
+
// Get pace-core version
|
|
362
|
+
function getPaceCoreVersion(packageJson) {
|
|
363
|
+
const deps = { ...packageJson.dependencies, ...packageJson.devDependencies };
|
|
364
|
+
return deps['@jmruthers/pace-core'] || 'unknown';
|
|
365
|
+
}
|
|
366
|
+
|
|
367
|
+
// Phase 1: Deterministic Checks
|
|
368
|
+
|
|
369
|
+
// Check file structure
|
|
370
|
+
function checkFileStructure(projectRoot) {
|
|
371
|
+
const issues = [];
|
|
372
|
+
const warnings = [];
|
|
373
|
+
|
|
374
|
+
const requiredDirs = ['src'];
|
|
375
|
+
const recommendedDirs = ['src/components', 'src/hooks', 'src/utils', 'src/pages'];
|
|
376
|
+
|
|
377
|
+
requiredDirs.forEach(dir => {
|
|
378
|
+
const dirPath = path.join(projectRoot, dir);
|
|
379
|
+
if (!fs.existsSync(dirPath)) {
|
|
380
|
+
issues.push({
|
|
381
|
+
type: 'missing-directory',
|
|
382
|
+
directory: dir,
|
|
383
|
+
severity: 'error',
|
|
384
|
+
message: `Required directory '${dir}' is missing`
|
|
385
|
+
});
|
|
386
|
+
}
|
|
387
|
+
});
|
|
388
|
+
|
|
389
|
+
recommendedDirs.forEach(dir => {
|
|
390
|
+
const dirPath = path.join(projectRoot, dir);
|
|
391
|
+
if (!fs.existsSync(dirPath)) {
|
|
392
|
+
warnings.push({
|
|
393
|
+
type: 'missing-directory',
|
|
394
|
+
directory: dir,
|
|
395
|
+
severity: 'warning',
|
|
396
|
+
message: `Recommended directory '${dir}' is missing`
|
|
397
|
+
});
|
|
398
|
+
}
|
|
399
|
+
});
|
|
400
|
+
|
|
401
|
+
return { issues, warnings };
|
|
402
|
+
}
|
|
403
|
+
|
|
404
|
+
// Check config files
|
|
405
|
+
function checkConfigFiles(projectRoot) {
|
|
406
|
+
const issues = [];
|
|
407
|
+
const warnings = [];
|
|
408
|
+
|
|
409
|
+
const requiredConfigs = [
|
|
410
|
+
{ file: 'package.json', severity: 'error' },
|
|
411
|
+
{ file: 'tsconfig.json', severity: 'error' },
|
|
412
|
+
{ file: 'vite.config.ts', severity: 'warning' },
|
|
413
|
+
{ file: 'vitest.config.ts', severity: 'warning' }
|
|
414
|
+
];
|
|
415
|
+
|
|
416
|
+
requiredConfigs.forEach(({ file, severity }) => {
|
|
417
|
+
const filePath = path.join(projectRoot, file);
|
|
418
|
+
if (!fs.existsSync(filePath)) {
|
|
419
|
+
if (severity === 'error') {
|
|
420
|
+
issues.push({
|
|
421
|
+
type: 'missing-config',
|
|
422
|
+
file,
|
|
423
|
+
severity,
|
|
424
|
+
message: `Required config file '${file}' is missing`
|
|
425
|
+
});
|
|
426
|
+
} else {
|
|
427
|
+
warnings.push({
|
|
428
|
+
type: 'missing-config',
|
|
429
|
+
file,
|
|
430
|
+
severity,
|
|
431
|
+
message: `Recommended config file '${file}' is missing`
|
|
432
|
+
});
|
|
433
|
+
}
|
|
434
|
+
}
|
|
435
|
+
});
|
|
436
|
+
|
|
437
|
+
return { issues, warnings };
|
|
438
|
+
}
|
|
439
|
+
|
|
440
|
+
// Check TypeScript strict mode
|
|
441
|
+
function checkTypeScriptConfig(projectRoot) {
|
|
442
|
+
const issues = [];
|
|
443
|
+
const warnings = [];
|
|
444
|
+
|
|
445
|
+
const tsconfigPath = path.join(projectRoot, 'tsconfig.json');
|
|
446
|
+
if (!fs.existsSync(tsconfigPath)) {
|
|
447
|
+
return { issues, warnings };
|
|
448
|
+
}
|
|
449
|
+
|
|
450
|
+
try {
|
|
451
|
+
const tsconfig = JSON.parse(fs.readFileSync(tsconfigPath, 'utf8'));
|
|
452
|
+
const compilerOptions = tsconfig.compilerOptions || {};
|
|
453
|
+
|
|
454
|
+
if (!compilerOptions.strict) {
|
|
455
|
+
issues.push({
|
|
456
|
+
type: 'typescript-config',
|
|
457
|
+
severity: 'error',
|
|
458
|
+
message: 'TypeScript strict mode is not enabled',
|
|
459
|
+
recommendation: 'Set "strict": true in tsconfig.json'
|
|
460
|
+
});
|
|
461
|
+
}
|
|
462
|
+
|
|
463
|
+
if (compilerOptions.noImplicitAny === false) {
|
|
464
|
+
issues.push({
|
|
465
|
+
type: 'typescript-config',
|
|
466
|
+
severity: 'error',
|
|
467
|
+
message: 'noImplicitAny is disabled',
|
|
468
|
+
recommendation: 'Set "noImplicitAny": true in tsconfig.json'
|
|
469
|
+
});
|
|
470
|
+
}
|
|
471
|
+
} catch (error) {
|
|
472
|
+
warnings.push({
|
|
473
|
+
type: 'typescript-config',
|
|
474
|
+
severity: 'warning',
|
|
475
|
+
message: `Could not parse tsconfig.json: ${error.message}`
|
|
476
|
+
});
|
|
477
|
+
}
|
|
478
|
+
|
|
479
|
+
return { issues, warnings };
|
|
480
|
+
}
|
|
481
|
+
|
|
482
|
+
// Check test coverage
|
|
483
|
+
function checkTestCoverage(projectRoot) {
|
|
484
|
+
const issues = [];
|
|
485
|
+
const warnings = [];
|
|
486
|
+
|
|
487
|
+
// Try to find coverage report
|
|
488
|
+
const coveragePaths = [
|
|
489
|
+
path.join(projectRoot, 'coverage', 'coverage-summary.json'),
|
|
490
|
+
path.join(projectRoot, 'coverage', 'coverage-final.json')
|
|
491
|
+
];
|
|
492
|
+
|
|
493
|
+
let coverageData = null;
|
|
494
|
+
for (const coveragePath of coveragePaths) {
|
|
495
|
+
if (fs.existsSync(coveragePath)) {
|
|
496
|
+
try {
|
|
497
|
+
coverageData = JSON.parse(fs.readFileSync(coveragePath, 'utf8'));
|
|
498
|
+
break;
|
|
499
|
+
} catch (error) {
|
|
500
|
+
// Continue to next path
|
|
501
|
+
}
|
|
502
|
+
}
|
|
503
|
+
}
|
|
504
|
+
|
|
505
|
+
if (!coverageData) {
|
|
506
|
+
warnings.push({
|
|
507
|
+
type: 'coverage',
|
|
508
|
+
severity: 'warning',
|
|
509
|
+
message: 'No coverage report found. Run tests with coverage to get accurate metrics.'
|
|
510
|
+
});
|
|
511
|
+
return { issues, warnings };
|
|
512
|
+
}
|
|
513
|
+
|
|
514
|
+
// Check coverage thresholds
|
|
515
|
+
const total = coverageData.total || {};
|
|
516
|
+
const lines = total.lines?.pct || 0;
|
|
517
|
+
const functions = total.functions?.pct || 0;
|
|
518
|
+
const branches = total.branches?.pct || 0;
|
|
519
|
+
const statements = total.statements?.pct || 0;
|
|
520
|
+
|
|
521
|
+
if (lines < 70) {
|
|
522
|
+
warnings.push({
|
|
523
|
+
type: 'coverage',
|
|
524
|
+
severity: 'warning',
|
|
525
|
+
message: `Line coverage is ${lines.toFixed(1)}%, below recommended 70%`
|
|
526
|
+
});
|
|
527
|
+
}
|
|
528
|
+
|
|
529
|
+
if (functions < 70) {
|
|
530
|
+
warnings.push({
|
|
531
|
+
type: 'coverage',
|
|
532
|
+
severity: 'warning',
|
|
533
|
+
message: `Function coverage is ${functions.toFixed(1)}%, below recommended 70%`
|
|
534
|
+
});
|
|
535
|
+
}
|
|
536
|
+
|
|
537
|
+
return { issues, warnings, metrics: { lines, functions, branches, statements } };
|
|
538
|
+
}
|
|
539
|
+
|
|
540
|
+
// Phase 2: Heuristic Checks
|
|
541
|
+
|
|
542
|
+
// Check for large files
|
|
543
|
+
function checkLargeFiles(projectRoot) {
|
|
544
|
+
const suggestions = [];
|
|
545
|
+
const maxLines = 500;
|
|
546
|
+
|
|
547
|
+
function scanDirectory(dir, relativePath = '') {
|
|
548
|
+
const items = fs.readdirSync(dir);
|
|
549
|
+
|
|
550
|
+
items.forEach(item => {
|
|
551
|
+
const fullPath = path.join(dir, item);
|
|
552
|
+
const stat = fs.statSync(fullPath);
|
|
553
|
+
|
|
554
|
+
if (stat.isDirectory() && !item.startsWith('.') && item !== 'node_modules' && item !== 'dist') {
|
|
555
|
+
scanDirectory(fullPath, path.join(relativePath, item));
|
|
556
|
+
} else if (stat.isFile() && /\.(ts|tsx|js|jsx)$/.test(item)) {
|
|
557
|
+
const content = fs.readFileSync(fullPath, 'utf8');
|
|
558
|
+
const lines = content.split('\n').length;
|
|
559
|
+
|
|
560
|
+
if (lines > maxLines) {
|
|
561
|
+
suggestions.push({
|
|
562
|
+
type: 'large-file',
|
|
563
|
+
file: path.join(relativePath, item),
|
|
564
|
+
lines,
|
|
565
|
+
severity: 'suggestion',
|
|
566
|
+
message: `File has ${lines} lines, consider splitting into smaller modules`
|
|
567
|
+
});
|
|
568
|
+
}
|
|
569
|
+
}
|
|
570
|
+
});
|
|
571
|
+
}
|
|
572
|
+
|
|
573
|
+
const srcPath = path.join(projectRoot, 'src');
|
|
574
|
+
if (fs.existsSync(srcPath)) {
|
|
575
|
+
scanDirectory(srcPath, 'src');
|
|
576
|
+
}
|
|
577
|
+
|
|
578
|
+
return suggestions;
|
|
579
|
+
}
|
|
580
|
+
|
|
581
|
+
// Check for god objects (heuristic)
|
|
582
|
+
function checkGodObjects(projectRoot) {
|
|
583
|
+
const suggestions = [];
|
|
584
|
+
|
|
585
|
+
// This is a simplified heuristic - in practice, you'd use AST parsing
|
|
586
|
+
function scanFile(filePath, relativePath) {
|
|
587
|
+
try {
|
|
588
|
+
const content = fs.readFileSync(filePath, 'utf8');
|
|
589
|
+
|
|
590
|
+
// Heuristic: Files with many exports might be god objects
|
|
591
|
+
const exportCount = (content.match(/export\s+(const|function|class|interface|type)/g) || []).length;
|
|
592
|
+
|
|
593
|
+
if (exportCount > 10) {
|
|
594
|
+
suggestions.push({
|
|
595
|
+
type: 'god-object',
|
|
596
|
+
file: relativePath,
|
|
597
|
+
exportCount,
|
|
598
|
+
severity: 'suggestion',
|
|
599
|
+
message: `File exports ${exportCount} items, consider splitting into smaller modules`
|
|
600
|
+
});
|
|
601
|
+
}
|
|
602
|
+
} catch (error) {
|
|
603
|
+
// Skip files we can't read
|
|
604
|
+
}
|
|
605
|
+
}
|
|
606
|
+
|
|
607
|
+
function scanDirectory(dir, relativePath = '') {
|
|
608
|
+
const items = fs.readdirSync(dir);
|
|
609
|
+
|
|
610
|
+
items.forEach(item => {
|
|
611
|
+
const fullPath = path.join(dir, item);
|
|
612
|
+
const stat = fs.statSync(fullPath);
|
|
613
|
+
|
|
614
|
+
if (stat.isDirectory() && !item.startsWith('.') && item !== 'node_modules' && item !== 'dist') {
|
|
615
|
+
scanDirectory(fullPath, path.join(relativePath, item));
|
|
616
|
+
} else if (stat.isFile() && /\.(ts|tsx|js|jsx)$/.test(item)) {
|
|
617
|
+
scanFile(fullPath, path.join(relativePath, item));
|
|
618
|
+
}
|
|
619
|
+
});
|
|
620
|
+
}
|
|
621
|
+
|
|
622
|
+
const srcPath = path.join(projectRoot, 'src');
|
|
623
|
+
if (fs.existsSync(srcPath)) {
|
|
624
|
+
scanDirectory(srcPath, 'src');
|
|
625
|
+
}
|
|
626
|
+
|
|
627
|
+
return suggestions;
|
|
628
|
+
}
|
|
629
|
+
|
|
630
|
+
// Generate markdown report
|
|
631
|
+
function generateMarkdownReport(auditResults, projectRoot, packageJson, paceCoreVersion) {
|
|
632
|
+
const timestamp = new Date();
|
|
633
|
+
// Format: yyyymmddhhmmss
|
|
634
|
+
const year = timestamp.getFullYear();
|
|
635
|
+
const month = String(timestamp.getMonth() + 1).padStart(2, '0');
|
|
636
|
+
const day = String(timestamp.getDate()).padStart(2, '0');
|
|
637
|
+
const hours = String(timestamp.getHours()).padStart(2, '0');
|
|
638
|
+
const minutes = String(timestamp.getMinutes()).padStart(2, '0');
|
|
639
|
+
const seconds = String(timestamp.getSeconds()).padStart(2, '0');
|
|
640
|
+
const timestampStr = `${year}${month}${day}${hours}${minutes}${seconds}`;
|
|
641
|
+
const filename = `audit-${timestampStr}.md`;
|
|
642
|
+
const auditDir = path.join(projectRoot, 'audit');
|
|
643
|
+
|
|
644
|
+
// Create audit directory if it doesn't exist
|
|
645
|
+
if (!fs.existsSync(auditDir)) {
|
|
646
|
+
fs.mkdirSync(auditDir, { recursive: true });
|
|
647
|
+
}
|
|
648
|
+
|
|
649
|
+
const reportPath = path.join(auditDir, filename);
|
|
650
|
+
|
|
651
|
+
// Calculate totals
|
|
652
|
+
const phase1Errors = auditResults.phase1.issues.length;
|
|
653
|
+
const phase1Warnings = auditResults.phase1.warnings.length;
|
|
654
|
+
const phase2Suggestions = auditResults.phase2.suggestions.length;
|
|
655
|
+
|
|
656
|
+
// Calculate compliance issues
|
|
657
|
+
const complianceIssues = auditResults.compliance
|
|
658
|
+
? (auditResults.compliance.customAuthCode.length +
|
|
659
|
+
auditResults.compliance.restrictedImports.length +
|
|
660
|
+
auditResults.compliance.duplicateComponents.length +
|
|
661
|
+
auditResults.compliance.duplicateHooks.length +
|
|
662
|
+
auditResults.compliance.duplicateUtils.length +
|
|
663
|
+
auditResults.compliance.unprotectedPages.length +
|
|
664
|
+
auditResults.compliance.directSupabaseAuth.length +
|
|
665
|
+
auditResults.compliance.providerSetupIssues.length +
|
|
666
|
+
auditResults.compliance.viteConfigIssues.length +
|
|
667
|
+
auditResults.compliance.routerSetupIssues.length +
|
|
668
|
+
auditResults.compliance.duplicateConfig.length)
|
|
669
|
+
: 0;
|
|
670
|
+
|
|
671
|
+
const totalIssues = phase1Errors + phase1Warnings + phase2Suggestions + complianceIssues;
|
|
672
|
+
|
|
673
|
+
// Calculate compliance score (0-100)
|
|
674
|
+
const maxIssues = 100; // Arbitrary max for scoring
|
|
675
|
+
const complianceScore = Math.max(0, 100 - Math.min(100, (totalIssues / maxIssues) * 100));
|
|
676
|
+
|
|
677
|
+
// Generate markdown
|
|
678
|
+
let markdown = `# pace-core Compliance Audit Report\n\n`;
|
|
679
|
+
markdown += `**Generated:** ${timestamp.toISOString()}\n`;
|
|
680
|
+
markdown += `**Project:** ${packageJson?.name || 'Unknown'}\n`;
|
|
681
|
+
markdown += `**pace-core Version:** ${paceCoreVersion}\n`;
|
|
682
|
+
markdown += `**Audit Tool Version:** 1.0.0\n\n`;
|
|
683
|
+
|
|
684
|
+
markdown += `## Executive Summary\n\n`;
|
|
685
|
+
markdown += `- **Total Issues:** ${totalIssues}\n`;
|
|
686
|
+
markdown += `- **Critical Failures:** ${phase1Errors} (Phase 1 - Must Fix)\n`;
|
|
687
|
+
markdown += `- **Warnings:** ${phase1Warnings} (Phase 1 - Should Fix)\n`;
|
|
688
|
+
markdown += `- **Suggestions:** ${phase2Suggestions} (Phase 2 - Consider)\n`;
|
|
689
|
+
markdown += `- **Compliance Issues:** ${complianceIssues} (pace-core Compliance Check)\n`;
|
|
690
|
+
markdown += `- **Compliance Score:** ${complianceScore.toFixed(1)}%\n\n`;
|
|
691
|
+
|
|
692
|
+
// Phase 1: Deterministic Checks
|
|
693
|
+
markdown += `## Phase 1: Deterministic Checks\n\n`;
|
|
694
|
+
|
|
695
|
+
// File Structure
|
|
696
|
+
if (auditResults.phase1.fileStructure.issues.length > 0 || auditResults.phase1.fileStructure.warnings.length > 0) {
|
|
697
|
+
markdown += `### File Structure\n\n`;
|
|
698
|
+
auditResults.phase1.fileStructure.issues.forEach(issue => {
|
|
699
|
+
markdown += `- [ ] ❌ **${issue.directory}**: ${issue.message}\n`;
|
|
700
|
+
});
|
|
701
|
+
auditResults.phase1.fileStructure.warnings.forEach(warning => {
|
|
702
|
+
markdown += `- [ ] ⚠️ **${warning.directory}**: ${warning.message}\n`;
|
|
703
|
+
});
|
|
704
|
+
markdown += `\n`;
|
|
705
|
+
} else {
|
|
706
|
+
markdown += `### File Structure\n\n`;
|
|
707
|
+
markdown += `- [x] ✅ All required directories present\n\n`;
|
|
708
|
+
}
|
|
709
|
+
|
|
710
|
+
// Config Files
|
|
711
|
+
if (auditResults.phase1.configFiles.issues.length > 0 || auditResults.phase1.configFiles.warnings.length > 0) {
|
|
712
|
+
markdown += `### Config Files\n\n`;
|
|
713
|
+
auditResults.phase1.configFiles.issues.forEach(issue => {
|
|
714
|
+
markdown += `- [ ] ❌ **${issue.file}**: ${issue.message}\n`;
|
|
715
|
+
});
|
|
716
|
+
auditResults.phase1.configFiles.warnings.forEach(warning => {
|
|
717
|
+
markdown += `- [ ] ⚠️ **${warning.file}**: ${warning.message}\n`;
|
|
718
|
+
});
|
|
719
|
+
markdown += `\n`;
|
|
720
|
+
} else {
|
|
721
|
+
markdown += `### Config Files\n\n`;
|
|
722
|
+
markdown += `- [x] ✅ All required config files present\n\n`;
|
|
723
|
+
}
|
|
724
|
+
|
|
725
|
+
// TypeScript Config
|
|
726
|
+
if (auditResults.phase1.typescript.issues.length > 0 || auditResults.phase1.typescript.warnings.length > 0) {
|
|
727
|
+
markdown += `### TypeScript Configuration\n\n`;
|
|
728
|
+
auditResults.phase1.typescript.issues.forEach(issue => {
|
|
729
|
+
markdown += `- [ ] ❌ ${issue.message}\n`;
|
|
730
|
+
if (issue.recommendation) {
|
|
731
|
+
markdown += ` - Recommendation: ${issue.recommendation}\n`;
|
|
732
|
+
}
|
|
733
|
+
});
|
|
734
|
+
auditResults.phase1.typescript.warnings.forEach(warning => {
|
|
735
|
+
markdown += `- [ ] ⚠️ ${warning.message}\n`;
|
|
736
|
+
});
|
|
737
|
+
markdown += `\n`;
|
|
738
|
+
} else {
|
|
739
|
+
markdown += `### TypeScript Configuration\n\n`;
|
|
740
|
+
markdown += `- [x] ✅ TypeScript strict mode enabled\n\n`;
|
|
741
|
+
}
|
|
742
|
+
|
|
743
|
+
// Test Coverage
|
|
744
|
+
if (auditResults.phase1.coverage.warnings.length > 0) {
|
|
745
|
+
markdown += `### Test Coverage\n\n`;
|
|
746
|
+
if (auditResults.phase1.coverage.metrics) {
|
|
747
|
+
const m = auditResults.phase1.coverage.metrics;
|
|
748
|
+
markdown += `**Current Coverage:**\n`;
|
|
749
|
+
markdown += `- Lines: ${m.lines.toFixed(1)}%\n`;
|
|
750
|
+
markdown += `- Functions: ${m.functions.toFixed(1)}%\n`;
|
|
751
|
+
markdown += `- Branches: ${m.branches.toFixed(1)}%\n`;
|
|
752
|
+
markdown += `- Statements: ${m.statements.toFixed(1)}%\n\n`;
|
|
753
|
+
}
|
|
754
|
+
auditResults.phase1.coverage.warnings.forEach(warning => {
|
|
755
|
+
markdown += `- [ ] ⚠️ ${warning.message}\n`;
|
|
756
|
+
});
|
|
757
|
+
markdown += `\n`;
|
|
758
|
+
} else if (auditResults.phase1.coverage.metrics) {
|
|
759
|
+
markdown += `### Test Coverage\n\n`;
|
|
760
|
+
const m = auditResults.phase1.coverage.metrics;
|
|
761
|
+
markdown += `- [x] ✅ Coverage meets requirements\n`;
|
|
762
|
+
markdown += ` - Lines: ${m.lines.toFixed(1)}%\n`;
|
|
763
|
+
markdown += ` - Functions: ${m.functions.toFixed(1)}%\n`;
|
|
764
|
+
markdown += ` - Branches: ${m.branches.toFixed(1)}%\n`;
|
|
765
|
+
markdown += ` - Statements: ${m.statements.toFixed(1)}%\n\n`;
|
|
766
|
+
}
|
|
767
|
+
|
|
768
|
+
// Phase 2: Heuristic Checks
|
|
769
|
+
markdown += `## Phase 2: Heuristic Checks (Suggestions)\n\n`;
|
|
770
|
+
|
|
771
|
+
if (auditResults.phase2.suggestions.length === 0) {
|
|
772
|
+
markdown += `- ✅ No suggestions at this time\n\n`;
|
|
773
|
+
} else {
|
|
774
|
+
// Group by type
|
|
775
|
+
const byType = {};
|
|
776
|
+
auditResults.phase2.suggestions.forEach(suggestion => {
|
|
777
|
+
if (!byType[suggestion.type]) {
|
|
778
|
+
byType[suggestion.type] = [];
|
|
779
|
+
}
|
|
780
|
+
byType[suggestion.type].push(suggestion);
|
|
781
|
+
});
|
|
782
|
+
|
|
783
|
+
Object.entries(byType).forEach(([type, suggestions]) => {
|
|
784
|
+
const typeName = type.split('-').map(w => w.charAt(0).toUpperCase() + w.slice(1)).join(' ');
|
|
785
|
+
markdown += `### ${typeName}\n\n`;
|
|
786
|
+
suggestions.forEach(suggestion => {
|
|
787
|
+
markdown += `- 💡 **${suggestion.file}**: ${suggestion.message}\n`;
|
|
788
|
+
if (suggestion.lines) {
|
|
789
|
+
markdown += ` - Lines: ${suggestion.lines}\n`;
|
|
790
|
+
}
|
|
791
|
+
if (suggestion.exportCount) {
|
|
792
|
+
markdown += ` - Exports: ${suggestion.exportCount}\n`;
|
|
793
|
+
}
|
|
794
|
+
});
|
|
795
|
+
markdown += `\n`;
|
|
796
|
+
});
|
|
797
|
+
}
|
|
798
|
+
|
|
799
|
+
// Compliance Check Results
|
|
800
|
+
markdown += `## pace-core Compliance Check\n\n`;
|
|
801
|
+
if (auditResults.compliance) {
|
|
802
|
+
markdown += complianceViolationsToMarkdown(auditResults.compliance);
|
|
803
|
+
} else {
|
|
804
|
+
markdown += `⚠️ Compliance check was not run. Run the audit script to include compliance results.\n\n`;
|
|
805
|
+
}
|
|
806
|
+
|
|
807
|
+
// Recommendations
|
|
808
|
+
markdown += `## Recommendations\n\n`;
|
|
809
|
+
|
|
810
|
+
if (phase1Errors > 0) {
|
|
811
|
+
markdown += `### Must Fix (Phase 1 Failures)\n\n`;
|
|
812
|
+
auditResults.phase1.issues.forEach(issue => {
|
|
813
|
+
markdown += `1. **Priority: High** - ${issue.message || issue.type}\n`;
|
|
814
|
+
if (issue.recommendation) {
|
|
815
|
+
markdown += ` - ${issue.recommendation}\n`;
|
|
816
|
+
}
|
|
817
|
+
});
|
|
818
|
+
markdown += `\n`;
|
|
819
|
+
}
|
|
820
|
+
|
|
821
|
+
if (phase1Warnings > 0) {
|
|
822
|
+
markdown += `### Should Fix (Phase 1 Warnings)\n\n`;
|
|
823
|
+
auditResults.phase1.warnings.forEach(warning => {
|
|
824
|
+
markdown += `1. **Priority: Medium** - ${warning.message || warning.type}\n`;
|
|
825
|
+
if (warning.recommendation) {
|
|
826
|
+
markdown += ` - ${warning.recommendation}\n`;
|
|
827
|
+
}
|
|
828
|
+
});
|
|
829
|
+
markdown += `\n`;
|
|
830
|
+
}
|
|
831
|
+
|
|
832
|
+
if (phase2Suggestions > 0) {
|
|
833
|
+
markdown += `### Consider (Phase 2 Suggestions)\n\n`;
|
|
834
|
+
auditResults.phase2.suggestions.slice(0, 10).forEach(suggestion => {
|
|
835
|
+
markdown += `1. **Priority: Low** - ${suggestion.message}\n`;
|
|
836
|
+
markdown += ` - File: ${suggestion.file}\n`;
|
|
837
|
+
});
|
|
838
|
+
if (phase2Suggestions > 10) {
|
|
839
|
+
markdown += `\n*... and ${phase2Suggestions - 10} more suggestions*\n`;
|
|
840
|
+
}
|
|
841
|
+
markdown += `\n`;
|
|
842
|
+
}
|
|
843
|
+
|
|
844
|
+
if (totalIssues === 0) {
|
|
845
|
+
markdown += `✅ **Excellent!** No issues found. Your codebase is fully compliant.\n\n`;
|
|
846
|
+
}
|
|
847
|
+
|
|
848
|
+
// Write report
|
|
849
|
+
fs.writeFileSync(reportPath, markdown, 'utf8');
|
|
850
|
+
|
|
851
|
+
return { reportPath, filename };
|
|
852
|
+
}
|
|
853
|
+
|
|
854
|
+
// Main function
|
|
855
|
+
function main() {
|
|
856
|
+
const projectRoot = findProjectRoot();
|
|
857
|
+
const packageJson = getPackageInfo(projectRoot);
|
|
858
|
+
const paceCoreVersion = getPaceCoreVersion(packageJson);
|
|
859
|
+
|
|
860
|
+
console.log(`${colors.cyan}${colors.bold}═══════════════════════════════════════════════════════════${colors.reset}`);
|
|
861
|
+
console.log(`${colors.cyan}${colors.bold} pace-core Comprehensive Audit${colors.reset}`);
|
|
862
|
+
console.log(`${colors.cyan}${colors.bold}═══════════════════════════════════════════════════════════${colors.reset}\n`);
|
|
863
|
+
console.log(`${colors.cyan}Project:${colors.reset} ${packageJson?.name || 'Unknown'}`);
|
|
864
|
+
console.log(`${colors.cyan}Root:${colors.reset} ${projectRoot}`);
|
|
865
|
+
console.log(`${colors.cyan}pace-core Version:${colors.reset} ${paceCoreVersion}\n`);
|
|
866
|
+
|
|
867
|
+
// Note: We don't call complianceChecker.main() because it calls process.exit()
|
|
868
|
+
// The compliance check output you see is from a previous run or separate execution
|
|
869
|
+
// We'll continue with Phase 1 and Phase 2 checks and generate markdown report
|
|
870
|
+
|
|
871
|
+
// Phase 1: Deterministic Checks
|
|
872
|
+
console.log(`\n${colors.cyan}Phase 1: Running deterministic checks...${colors.reset}`);
|
|
873
|
+
const fileStructure = checkFileStructure(projectRoot);
|
|
874
|
+
const configFiles = checkConfigFiles(projectRoot);
|
|
875
|
+
const typescript = checkTypeScriptConfig(projectRoot);
|
|
876
|
+
const coverage = checkTestCoverage(projectRoot);
|
|
877
|
+
|
|
878
|
+
const phase1 = {
|
|
879
|
+
issues: [
|
|
880
|
+
...fileStructure.issues,
|
|
881
|
+
...configFiles.issues,
|
|
882
|
+
...typescript.issues,
|
|
883
|
+
...coverage.issues
|
|
884
|
+
],
|
|
885
|
+
warnings: [
|
|
886
|
+
...fileStructure.warnings,
|
|
887
|
+
...configFiles.warnings,
|
|
888
|
+
...typescript.warnings,
|
|
889
|
+
...coverage.warnings
|
|
890
|
+
],
|
|
891
|
+
fileStructure,
|
|
892
|
+
configFiles,
|
|
893
|
+
typescript,
|
|
894
|
+
coverage
|
|
895
|
+
};
|
|
896
|
+
|
|
897
|
+
// Phase 2: Heuristic Checks
|
|
898
|
+
console.log(`${colors.cyan}Phase 2: Running heuristic checks...${colors.reset}`);
|
|
899
|
+
const largeFiles = checkLargeFiles(projectRoot);
|
|
900
|
+
const godObjects = checkGodObjects(projectRoot);
|
|
901
|
+
|
|
902
|
+
const phase2 = {
|
|
903
|
+
suggestions: [
|
|
904
|
+
...largeFiles,
|
|
905
|
+
...godObjects
|
|
906
|
+
]
|
|
907
|
+
};
|
|
908
|
+
|
|
909
|
+
// Compliance Check
|
|
910
|
+
console.log(`${colors.cyan}Running pace-core compliance check...${colors.reset}`);
|
|
911
|
+
let complianceResults = null;
|
|
912
|
+
try {
|
|
913
|
+
complianceResults = runComplianceCheck(projectRoot);
|
|
914
|
+
console.log(`${colors.green}✓ Compliance check complete${colors.reset}`);
|
|
915
|
+
} catch (error) {
|
|
916
|
+
console.error(`${colors.red}Compliance check failed: ${error.message}${colors.reset}`);
|
|
917
|
+
if (error.stack) {
|
|
918
|
+
console.error(`${colors.red}Stack: ${error.stack}${colors.reset}`);
|
|
919
|
+
}
|
|
920
|
+
console.error(`${colors.yellow}Continuing with other audit checks...${colors.reset}`);
|
|
921
|
+
}
|
|
922
|
+
|
|
923
|
+
// Generate report
|
|
924
|
+
console.log(`\n${colors.cyan}Generating audit report...${colors.reset}`);
|
|
925
|
+
const auditResults = { phase1, phase2, compliance: complianceResults };
|
|
926
|
+
const { reportPath, filename } = generateMarkdownReport(
|
|
927
|
+
auditResults,
|
|
928
|
+
projectRoot,
|
|
929
|
+
packageJson,
|
|
930
|
+
paceCoreVersion
|
|
931
|
+
);
|
|
932
|
+
|
|
933
|
+
console.log(`\n${colors.green}${colors.bold}✅ Audit complete!${colors.reset}`);
|
|
934
|
+
console.log(`${colors.green}Report saved to: ${reportPath}${colors.reset}\n`);
|
|
935
|
+
|
|
936
|
+
// Summary
|
|
937
|
+
const totalErrors = phase1.issues.length;
|
|
938
|
+
const totalWarnings = phase1.warnings.length;
|
|
939
|
+
const totalSuggestions = phase2.suggestions.length;
|
|
940
|
+
|
|
941
|
+
console.log(`${colors.bold}Summary:${colors.reset}`);
|
|
942
|
+
console.log(` ${colors.red}Critical Failures:${colors.reset} ${totalErrors}`);
|
|
943
|
+
console.log(` ${colors.yellow}Warnings:${colors.reset} ${totalWarnings}`);
|
|
944
|
+
console.log(` ${colors.blue}Suggestions:${colors.reset} ${totalSuggestions}`);
|
|
945
|
+
console.log(`\n${colors.cyan}See ${filename} for detailed report.${colors.reset}\n`);
|
|
946
|
+
|
|
947
|
+
process.exit(totalErrors > 0 ? 1 : 0);
|
|
948
|
+
}
|
|
949
|
+
|
|
950
|
+
// Run if called directly
|
|
951
|
+
if (require.main === module) {
|
|
952
|
+
try {
|
|
953
|
+
main();
|
|
954
|
+
} catch (error) {
|
|
955
|
+
console.error(`${colors.red}Error: ${error.message}${colors.reset}`);
|
|
956
|
+
console.error(error.stack);
|
|
957
|
+
process.exit(1);
|
|
958
|
+
}
|
|
959
|
+
}
|
|
960
|
+
|
|
961
|
+
module.exports = { main };
|