@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.
- package/CHANGELOG.md +62 -0
- package/README.md +7 -1
- package/cursor-rules/00-pace-core-compliance.mdc +299 -0
- package/cursor-rules/01-standards-compliance.mdc +244 -0
- package/cursor-rules/02-project-structure.mdc +200 -0
- package/cursor-rules/03-solid-principles.mdc +222 -0
- package/cursor-rules/04-testing-standards.mdc +268 -0
- package/cursor-rules/05-bug-reports-and-features.mdc +246 -0
- package/cursor-rules/06-code-quality.mdc +309 -0
- package/cursor-rules/07-tech-stack-compliance.mdc +214 -0
- package/cursor-rules/08-markup-quality.mdc +452 -0
- package/cursor-rules/CHANGELOG.md +119 -0
- package/cursor-rules/README.md +192 -0
- package/dist/{AuthService-DjnJHDtC.d.ts → AuthService-BPvc3Ka0.d.ts} +54 -0
- package/dist/{DataTable-Be6dH_dR.d.ts → DataTable-BMRU8a1j.d.ts} +34 -2
- package/dist/{DataTable-5FU7IESH.js → DataTable-TPTKCX4D.js} +10 -9
- package/dist/{PublicPageProvider-C0Sm_e5k.d.ts → PublicPageProvider-DC6kCaqf.d.ts} +385 -261
- package/dist/{UnifiedAuthProvider-RGJTDE2C.js → UnifiedAuthProvider-CH6Z342H.js} +3 -3
- package/dist/{UnifiedAuthProvider-185Ih4dj.d.ts → UnifiedAuthProvider-CVcTjx-d.d.ts} +29 -0
- package/dist/{api-N774RPUA.js → api-MVVQZLJI.js} +2 -2
- package/dist/{chunk-KNC55RTG.js → chunk-24UVZUZG.js} +90 -54
- package/dist/chunk-24UVZUZG.js.map +1 -0
- package/dist/{chunk-HWIIPPNI.js → chunk-2UOI2FG5.js} +20 -20
- package/dist/chunk-2UOI2FG5.js.map +1 -0
- package/dist/{chunk-E3SPN4VZ 5.js → chunk-3XC4CPTD.js} +4345 -3986
- package/dist/chunk-3XC4CPTD.js.map +1 -0
- package/dist/{chunk-7EQTDTTJ.js → chunk-6J4GEEJR.js} +172 -45
- package/dist/chunk-6J4GEEJR.js.map +1 -0
- package/dist/{chunk-6C4YBBJM 5.js → chunk-6SOIHG6Z.js} +1 -1
- package/dist/chunk-6SOIHG6Z.js.map +1 -0
- package/dist/{chunk-7FLMSG37.js → chunk-EHMR7VYL.js} +25 -25
- package/dist/chunk-EHMR7VYL.js.map +1 -0
- package/dist/{chunk-I7PSE6JW.js → chunk-F2IMUDXZ.js} +2 -75
- package/dist/chunk-F2IMUDXZ.js.map +1 -0
- package/dist/{chunk-QWWZ5CAQ.js → chunk-FFQEQTNW.js} +7 -9
- package/dist/chunk-FFQEQTNW.js.map +1 -0
- package/dist/chunk-FMUCXFII.js +76 -0
- package/dist/chunk-FMUCXFII.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-SQGMNID3.js → chunk-L4OXEN46.js} +4 -5
- package/dist/chunk-L4OXEN46.js.map +1 -0
- package/dist/{chunk-R77UEZ4E 3.js → chunk-M43Y4SSO.js} +1 -1
- package/dist/chunk-M43Y4SSO.js.map +1 -0
- package/dist/{chunk-IIELH4DL.js → chunk-MMZ7JXPU.js} +60 -223
- package/dist/chunk-MMZ7JXPU.js.map +1 -0
- package/dist/{chunk-NOAYCWCX 5.js → chunk-NECFR5MM.js} +394 -312
- package/dist/chunk-NECFR5MM.js.map +1 -0
- package/dist/{chunk-BC4IJKSL.js → chunk-SFZUDBL5.js} +40 -4
- package/dist/chunk-SFZUDBL5.js.map +1 -0
- package/dist/{chunk-XNXXZ43G.js → chunk-XWQCNGTQ.js} +748 -364
- package/dist/chunk-XWQCNGTQ.js.map +1 -0
- package/dist/components.d.ts +6 -6
- package/dist/components.js +15 -12
- package/dist/components.js.map +1 -1
- package/dist/{functions-D_kgHktt.d.ts → functions-DHebl8-F.d.ts} +1 -1
- package/dist/hooks.d.ts +59 -126
- package/dist/hooks.js +19 -28
- package/dist/hooks.js.map +1 -1
- package/dist/index.d.ts +63 -16
- package/dist/index.js +23 -24
- package/dist/index.js.map +1 -1
- package/dist/providers.d.ts +21 -3
- package/dist/providers.js +2 -2
- package/dist/rbac/index.d.ts +146 -115
- package/dist/rbac/index.js +8 -11
- package/dist/theming/runtime.d.ts +1 -13
- package/dist/theming/runtime.js +1 -1
- package/dist/{timezone-_pgH8qrY.d.ts → timezone-CHhWg6b4.d.ts} +3 -10
- package/dist/{types-UU913iLA.d.ts → types-BeoeWV5I.d.ts} +8 -0
- package/dist/{types-CEpcvwwF.d.ts → types-CkbwOr4Y.d.ts} +6 -0
- package/dist/types.d.ts +2 -2
- package/dist/{usePublicRouteParams-TZe0gy-4.d.ts → usePublicRouteParams-1oMokgLF.d.ts} +34 -4
- package/dist/{useToast-C8gR5ir4.d.ts → useToast-AyaT-x7p.d.ts} +2 -2
- package/dist/utils.d.ts +4 -5
- package/dist/utils.js +15 -15
- package/dist/utils.js.map +1 -1
- package/docs/api/README.md +7 -1
- package/docs/api/classes/ColumnFactory.md +8 -8
- package/docs/api/classes/InvalidScopeError.md +4 -4
- package/docs/api/classes/Logger.md +1 -1
- package/docs/api/classes/MissingUserContextError.md +4 -4
- package/docs/api/classes/OrganisationContextRequiredError.md +4 -4
- package/docs/api/classes/PermissionDeniedError.md +4 -4
- package/docs/api/classes/RBACAuditManager.md +1 -1
- package/docs/api/classes/RBACCache.md +1 -1
- package/docs/api/classes/RBACEngine.md +1 -1
- package/docs/api/classes/RBACError.md +4 -4
- package/docs/api/classes/RBACNotInitializedError.md +4 -4
- package/docs/api/classes/SecureSupabaseClient.md +18 -15
- 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 +4 -4
- package/docs/api/interfaces/AutocompleteOptions.md +1 -1
- package/docs/api/interfaces/AvatarProps.md +1 -1
- package/docs/api/interfaces/BadgeProps.md +9 -2
- package/docs/api/interfaces/ButtonProps.md +7 -4
- package/docs/api/interfaces/CalendarProps.md +8 -5
- package/docs/api/interfaces/CardProps.md +8 -5
- 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 +9 -9
- package/docs/api/interfaces/DataRecord.md +1 -1
- package/docs/api/interfaces/DataTableAction.md +24 -21
- package/docs/api/interfaces/DataTableColumn.md +31 -31
- package/docs/api/interfaces/DataTableProps.md +1 -1
- package/docs/api/interfaces/DataTableToolbarButton.md +7 -7
- package/docs/api/interfaces/DatabaseComplianceResult.md +1 -1
- package/docs/api/interfaces/DatabaseIssue.md +1 -1
- package/docs/api/interfaces/EmptyStateConfig.md +5 -5
- package/docs/api/interfaces/EnhancedNavigationMenuProps.md +1 -1
- package/docs/api/interfaces/ErrorBoundaryProps.md +147 -0
- package/docs/api/interfaces/ErrorBoundaryProviderProps.md +36 -0
- package/docs/api/interfaces/ErrorBoundaryState.md +75 -0
- package/docs/api/interfaces/EventAppRoleData.md +1 -1
- package/docs/api/interfaces/ExportColumn.md +1 -1
- package/docs/api/interfaces/ExportOptions.md +8 -8
- package/docs/api/interfaces/FileDisplayProps.md +1 -1
- 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 +26 -23
- package/docs/api/interfaces/FooterProps.md +10 -8
- package/docs/api/interfaces/FormFieldProps.md +10 -10
- 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 +7 -4
- package/docs/api/interfaces/LabelProps.md +1 -1
- package/docs/api/interfaces/LoggerConfig.md +1 -1
- package/docs/api/interfaces/LoginFormProps.md +14 -11
- package/docs/api/interfaces/NavigationAccessRecord.md +1 -1
- package/docs/api/interfaces/NavigationContextType.md +1 -1
- package/docs/api/interfaces/NavigationGuardProps.md +1 -1
- package/docs/api/interfaces/NavigationItem.md +11 -11
- package/docs/api/interfaces/NavigationMenuProps.md +15 -15
- 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 +30 -27
- package/docs/api/interfaces/PaceLoginPageProps.md +6 -4
- package/docs/api/interfaces/PageAccessRecord.md +1 -1
- package/docs/api/interfaces/PagePermissionContextType.md +1 -1
- package/docs/api/interfaces/PagePermissionGuardProps.md +1 -1
- 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 +1 -1
- package/docs/api/interfaces/ProgressProps.md +1 -1
- package/docs/api/interfaces/ProtectedRouteProps.md +7 -26
- package/docs/api/interfaces/PublicPageFooterProps.md +9 -9
- package/docs/api/interfaces/PublicPageHeaderProps.md +10 -10
- package/docs/api/interfaces/PublicPageLayoutProps.md +7 -20
- 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 +1 -1
- 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 +1 -1
- 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 +1 -1
- package/docs/api/interfaces/RBACRoleGrantResult.md +1 -1
- package/docs/api/interfaces/RBACRoleRevokeParams.md +1 -1
- package/docs/api/interfaces/RBACRoleRevokeResult.md +1 -1
- package/docs/api/interfaces/RBACRoleValidateParams.md +1 -1
- package/docs/api/interfaces/RBACRoleValidateResult.md +1 -1
- package/docs/api/interfaces/RBACRolesListParams.md +1 -1
- package/docs/api/interfaces/RBACRolesListResult.md +1 -1
- 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 +1 -1
- package/docs/api/interfaces/RouteConfig.md +1 -1
- package/docs/api/interfaces/RuntimeComplianceResult.md +1 -1
- package/docs/api/interfaces/SecureDataContextType.md +9 -9
- package/docs/api/interfaces/SecureDataProviderProps.md +8 -8
- package/docs/api/interfaces/SessionRestorationLoaderProps.md +3 -3
- 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 +3 -3
- package/docs/api/interfaces/TextareaProps.md +1 -1
- package/docs/api/interfaces/ToastActionElement.md +4 -1
- package/docs/api/interfaces/ToastProps.md +1 -1
- package/docs/api/interfaces/UnifiedAuthContextType.md +58 -55
- package/docs/api/interfaces/UnifiedAuthProviderProps.md +15 -13
- package/docs/api/interfaces/UseFormDialogOptions.md +1 -1
- package/docs/api/interfaces/UseFormDialogReturn.md +1 -1
- package/docs/api/interfaces/UseInactivityTrackerOptions.md +11 -9
- package/docs/api/interfaces/UseInactivityTrackerReturn.md +8 -8
- package/docs/api/interfaces/UsePublicEventLogoOptions.md +6 -6
- package/docs/api/interfaces/UsePublicEventLogoReturn.md +9 -6
- package/docs/api/interfaces/UsePublicEventOptions.md +3 -3
- package/docs/api/interfaces/UsePublicEventReturn.md +8 -5
- package/docs/api/interfaces/UsePublicFileDisplayOptions.md +4 -4
- package/docs/api/interfaces/UsePublicFileDisplayReturn.md +12 -9
- package/docs/api/interfaces/UsePublicRouteParamsReturn.md +10 -7
- package/docs/api/interfaces/UseResolvedScopeOptions.md +1 -1
- package/docs/api/interfaces/UseResolvedScopeReturn.md +1 -1
- package/docs/api/interfaces/UseResourcePermissionsOptions.md +1 -1
- package/docs/api/interfaces/UserEventAccess.md +14 -11
- package/docs/api/interfaces/UserMenuProps.md +8 -6
- package/docs/api/interfaces/UserProfile.md +1 -1
- package/docs/api/modules.md +575 -634
- package/docs/architecture/database-schema-requirements.md +161 -0
- package/docs/core-concepts/rbac-system.md +3 -3
- package/docs/documentation-index.md +2 -4
- package/docs/getting-started/cursor-rules.md +263 -0
- package/docs/getting-started/installation-guide.md +6 -1
- package/docs/getting-started/quick-start.md +6 -1
- package/docs/migration/DOCUMENTATION_STRUCTURE.md +441 -0
- package/docs/migration/MIGRATION_GUIDE.md +6 -28
- package/docs/migration/README.md +52 -6
- package/docs/migration/V0.5.190_TO_V0.6.1_MIGRATION.md +1153 -0
- package/docs/migration/V0.6.0_REACT_19_MIGRATION.md +227 -0
- package/docs/migration/database-changes-december-2025.md +3 -3
- package/docs/rbac/event-based-apps.md +1 -1
- package/docs/rbac/getting-started.md +1 -1
- package/docs/rbac/quick-start.md +1 -1
- package/docs/standards/README.md +40 -0
- package/docs/troubleshooting/migration.md +4 -4
- package/examples/PublicPages/PublicEventPage.tsx +1 -1
- package/package.json +12 -6
- package/scripts/audit/core/checks/accessibility.cjs +197 -0
- package/scripts/audit/core/checks/api-usage.cjs +191 -0
- package/scripts/audit/core/checks/bundle.cjs +142 -0
- package/scripts/{check-pace-core-compliance.cjs → audit/core/checks/compliance.cjs} +737 -691
- package/scripts/audit/core/checks/config.cjs +54 -0
- package/scripts/audit/core/checks/coverage.cjs +84 -0
- package/scripts/audit/core/checks/dependencies.cjs +454 -0
- package/scripts/audit/core/checks/documentation.cjs +203 -0
- package/scripts/audit/core/checks/environment.cjs +128 -0
- package/scripts/audit/core/checks/error-handling.cjs +299 -0
- package/scripts/audit/core/checks/forms.cjs +172 -0
- package/scripts/audit/core/checks/heuristics.cjs +68 -0
- package/scripts/audit/core/checks/hooks.cjs +334 -0
- package/scripts/audit/core/checks/imports.cjs +244 -0
- package/scripts/audit/core/checks/performance.cjs +325 -0
- package/scripts/audit/core/checks/routes.cjs +117 -0
- package/scripts/audit/core/checks/state.cjs +130 -0
- package/scripts/audit/core/checks/structure.cjs +65 -0
- package/scripts/audit/core/checks/style.cjs +584 -0
- package/scripts/audit/core/checks/testing.cjs +122 -0
- package/scripts/audit/core/checks/typescript.cjs +61 -0
- package/scripts/audit/core/scanner.cjs +199 -0
- package/scripts/audit/core/utils.cjs +137 -0
- package/scripts/audit/index.cjs +223 -0
- package/scripts/audit/reporters/console.cjs +151 -0
- package/scripts/audit/reporters/json.cjs +54 -0
- package/scripts/audit/reporters/markdown.cjs +124 -0
- package/scripts/audit-consuming-app.cjs +86 -0
- package/scripts/build-docs/build-decision.js +240 -0
- package/scripts/build-docs/cache-utils.js +105 -0
- package/scripts/build-docs/content-normalization.js +150 -0
- package/scripts/build-docs/file-utils.js +105 -0
- package/scripts/build-docs/git-utils.js +86 -0
- package/scripts/build-docs/hash-utils.js +116 -0
- package/scripts/build-docs/typedoc-runner.js +220 -0
- package/scripts/build-docs-incremental.js +77 -913
- package/scripts/install-cursor-rules.cjs +236 -0
- package/scripts/utils/command-runner.js +16 -11
- package/scripts/validate-formats.js +61 -56
- package/scripts/validate-master.js +74 -69
- package/scripts/validate-pre-publish.js +70 -65
- package/src/__tests__/helpers/test-providers.tsx +1 -1
- package/src/__tests__/helpers/test-utils.tsx +1 -1
- package/src/__tests__/hooks/usePermissions.test.ts +2 -2
- package/src/components/Alert/Alert.test.tsx +12 -18
- package/src/components/Alert/Alert.tsx +5 -7
- package/src/components/Avatar/Avatar.test.tsx +4 -4
- package/src/components/Badge/Badge.tsx +16 -4
- package/src/components/Button/Button.tsx +27 -4
- package/src/components/Calendar/Calendar.tsx +9 -3
- package/src/components/Card/Card.tsx +4 -0
- package/src/components/Checkbox/Checkbox.test.tsx +12 -12
- package/src/components/Checkbox/Checkbox.tsx +2 -2
- package/src/components/DataTable/DataTable.test.tsx +57 -93
- package/src/components/DataTable/DataTable.tsx +40 -6
- package/src/components/DataTable/__tests__/DataTableCore.test-setup.ts +5 -6
- package/src/components/DataTable/__tests__/pagination.modes.test.tsx +29 -7
- package/src/components/DataTable/__tests__/ssr.strict-mode.test.tsx +12 -12
- package/src/components/DataTable/__tests__/test-utils/sharedTestUtils.tsx +2 -3
- package/src/components/DataTable/components/AccessDeniedPage.tsx +17 -26
- package/src/components/DataTable/components/ActionButtons.tsx +10 -7
- package/src/components/DataTable/components/BulkOperationsDropdown.tsx +2 -2
- package/src/components/DataTable/components/ColumnFilter.tsx +10 -0
- package/src/components/DataTable/components/ColumnVisibilityDropdown.tsx +12 -0
- package/src/components/DataTable/components/DataTableBody.tsx +8 -0
- package/src/components/DataTable/components/DataTableCore.tsx +200 -561
- package/src/components/DataTable/components/DataTableErrorBoundary.tsx +11 -0
- package/src/components/DataTable/components/DataTableLayout.tsx +559 -0
- package/src/components/DataTable/components/DataTableModals.tsx +9 -1
- package/src/components/DataTable/components/DataTableToolbar.tsx +8 -0
- package/src/components/DataTable/components/DraggableColumnHeader.tsx +12 -0
- package/src/components/DataTable/components/EditFields.tsx +307 -0
- package/src/components/DataTable/components/EditableRow.tsx +9 -1
- package/src/components/DataTable/components/EmptyState.tsx +10 -0
- package/src/components/DataTable/components/FilterRow.tsx +12 -0
- package/src/components/DataTable/components/GroupHeader.tsx +12 -0
- package/src/components/DataTable/components/GroupingDropdown.tsx +12 -0
- package/src/components/DataTable/components/ImportModal.tsx +7 -0
- package/src/components/DataTable/components/LoadingState.tsx +6 -0
- package/src/components/DataTable/components/PaginationControls.tsx +16 -1
- package/src/components/DataTable/components/RowComponent.tsx +391 -0
- package/src/components/DataTable/components/UnifiedTableBody.tsx +62 -852
- package/src/components/DataTable/components/VirtualizedDataTable.tsx +16 -4
- package/src/components/DataTable/components/__tests__/AccessDeniedPage.test.tsx +4 -2
- 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/components/cellValueUtils.ts +40 -0
- package/src/components/DataTable/components/hooks/useImportModalFocus.ts +53 -0
- package/src/components/DataTable/components/hooks/usePermissionTracking.ts +126 -0
- package/src/components/DataTable/context/DataTableContext.tsx +50 -0
- package/src/components/DataTable/core/ColumnFactory.ts +31 -0
- package/src/components/DataTable/core/DataTableContext.tsx +32 -1
- package/src/components/DataTable/hooks/useColumnOrderPersistence.ts +10 -0
- package/src/components/DataTable/hooks/useColumnReordering.ts +14 -2
- package/src/components/DataTable/hooks/useColumnVisibilityPersistence.ts +10 -0
- package/src/components/DataTable/hooks/useDataTableDataPipeline.ts +16 -0
- package/src/components/DataTable/hooks/useDataTablePermissions.ts +124 -32
- package/src/components/DataTable/hooks/useDataTableState.ts +35 -1
- package/src/components/DataTable/hooks/useEffectiveColumnOrder.ts +12 -0
- package/src/components/DataTable/hooks/useKeyboardNavigation.ts +2 -2
- package/src/components/DataTable/hooks/useServerSideDataEffect.ts +11 -0
- package/src/components/DataTable/hooks/useTableColumns.ts +8 -0
- package/src/components/DataTable/hooks/useTableHandlers.ts +14 -0
- package/src/components/DataTable/styles.ts +6 -6
- package/src/components/DataTable/types.ts +6 -10
- package/src/components/DataTable/utils/a11yUtils.ts +7 -0
- package/src/components/DataTable/utils/debugTools.ts +18 -113
- package/src/components/DataTable/utils/errorHandling.ts +12 -0
- package/src/components/DataTable/utils/exportUtils.ts +9 -0
- package/src/components/DataTable/utils/flexibleImport.ts +12 -48
- package/src/components/DataTable/utils/paginationUtils.ts +8 -0
- package/src/components/DataTable/utils/performanceUtils.ts +5 -1
- package/src/components/DatePickerWithTimezone/DatePickerWithTimezone.test.tsx +8 -14
- package/src/components/Dialog/Dialog.tsx +8 -7
- package/src/components/ErrorBoundary/ErrorBoundary.test.tsx +180 -1
- package/src/components/ErrorBoundary/ErrorBoundary.tsx +46 -6
- package/src/components/ErrorBoundary/ErrorBoundaryContext.tsx +129 -0
- package/src/components/ErrorBoundary/index.ts +27 -2
- package/src/components/EventSelector/EventSelector.tsx +4 -1
- package/src/components/FileDisplay/FileDisplay.test.tsx +2 -2
- package/src/components/FileDisplay/FileDisplay.tsx +32 -18
- package/src/components/FileUpload/FileUpload.tsx +22 -2
- package/src/components/Footer/Footer.test.tsx +16 -16
- package/src/components/Footer/Footer.tsx +15 -12
- package/src/components/Form/Form.test.tsx +36 -15
- package/src/components/Form/Form.tsx +31 -26
- package/src/components/Header/Header.tsx +22 -11
- package/src/components/InactivityWarningModal/InactivityWarningModal.test.tsx +40 -40
- package/src/components/InactivityWarningModal/InactivityWarningModal.tsx +1 -1
- package/src/components/Input/Input.test.tsx +2 -2
- package/src/components/Input/Input.tsx +36 -34
- package/src/components/Label/Label.tsx +1 -1
- package/src/components/LoadingSpinner/LoadingSpinner.test.tsx +4 -4
- package/src/components/LoadingSpinner/LoadingSpinner.tsx +1 -1
- package/src/components/LoginForm/LoginForm.test.tsx +42 -42
- package/src/components/LoginForm/LoginForm.tsx +12 -8
- package/src/components/NavigationMenu/NavigationMenu.tsx +15 -514
- package/src/components/NavigationMenu/types.ts +56 -0
- package/src/components/NavigationMenu/useNavigationFiltering.ts +390 -0
- package/src/components/OrganisationSelector/OrganisationSelector.tsx +3 -0
- package/src/components/PaceAppLayout/PaceAppLayout.performance.test.tsx +1 -1
- package/src/components/PaceAppLayout/PaceAppLayout.test.tsx +54 -52
- package/src/components/PaceAppLayout/PaceAppLayout.tsx +33 -12
- package/src/components/PaceAppLayout/README.md +1 -1
- package/src/components/PaceAppLayout/test-setup.tsx +1 -2
- package/src/components/PaceLoginPage/PaceLoginPage.tsx +4 -1
- package/src/components/PasswordChange/PasswordChangeForm.test.tsx +33 -33
- package/src/components/PasswordChange/PasswordChangeForm.tsx +10 -1
- package/src/components/Progress/Progress.tsx +1 -1
- package/src/components/ProtectedRoute/ProtectedRoute.tsx +3 -9
- package/src/components/PublicLayout/PublicPageLayout.tsx +3 -6
- package/src/components/PublicLayout/PublicPageProvider.tsx +4 -0
- package/src/components/Select/Select.tsx +95 -438
- package/src/components/Select/context.ts +23 -0
- package/src/components/Select/hooks/useSelectEvents.ts +87 -0
- package/src/components/Select/hooks/useSelectSearch.ts +91 -0
- package/src/components/Select/hooks/useSelectState.ts +104 -0
- package/src/components/Select/index.ts +9 -1
- package/src/components/Select/types.ts +123 -0
- package/src/components/Select/utils/text.ts +26 -0
- package/src/components/SessionRestorationLoader/SessionRestorationLoader.tsx +5 -6
- package/src/components/Switch/Switch.tsx +4 -4
- package/src/components/Table/Table.tsx +1 -1
- package/src/components/Tabs/Tabs.tsx +1 -1
- package/src/components/Textarea/Textarea.tsx +27 -29
- package/src/components/Toast/Toast.tsx +5 -1
- package/src/components/Tooltip/Tooltip.tsx +3 -3
- package/src/components/UserMenu/UserMenu.test.tsx +24 -11
- package/src/components/UserMenu/UserMenu.tsx +22 -19
- package/src/components/index.ts +2 -2
- package/src/hooks/__tests__/hooks.integration.test.tsx +80 -55
- package/src/hooks/__tests__/index.unit.test.ts +2 -5
- package/src/hooks/__tests__/useStorage.unit.test.ts +36 -36
- package/src/hooks/index.ts +1 -2
- package/src/hooks/public/usePublicEvent.ts +5 -1
- package/src/hooks/public/usePublicEventLogo.ts +5 -1
- package/src/hooks/public/usePublicFileDisplay.ts +4 -0
- package/src/hooks/public/usePublicRouteParams.ts +5 -1
- package/src/hooks/services/useAuth.ts +32 -0
- package/src/hooks/services/useCurrentEvent.ts +6 -0
- package/src/hooks/services/useCurrentOrganisation.ts +6 -0
- package/src/hooks/useDataTableState.ts +8 -18
- package/src/hooks/useDebounce.ts +9 -0
- package/src/hooks/useEventTheme.ts +6 -0
- package/src/hooks/useFileDisplay.ts +4 -0
- package/src/hooks/useFileReference.ts +25 -7
- package/src/hooks/useFileUrl.ts +11 -1
- package/src/hooks/useFocusManagement.ts +16 -2
- package/src/hooks/useFocusTrap.ts +7 -4
- package/src/hooks/useFormDialog.ts +8 -7
- package/src/hooks/useInactivityTracker.ts +4 -1
- package/src/hooks/useKeyboardShortcuts.ts +4 -0
- package/src/hooks/useOrganisationPermissions.ts +4 -0
- package/src/hooks/useOrganisationSecurity.ts +4 -0
- package/src/hooks/usePerformanceMonitor.ts +4 -0
- package/src/hooks/usePermissionCache.ts +8 -1
- package/src/hooks/useQueryCache.ts +12 -1
- package/src/hooks/useSessionRestoration.ts +4 -0
- package/src/hooks/useStorage.ts +4 -0
- package/src/hooks/useToast.ts +3 -3
- package/src/index.ts +2 -1
- package/src/providers/__tests__/OrganisationProvider.test.tsx +115 -49
- package/src/providers/__tests__/ProviderLifecycle.test.tsx +21 -6
- package/src/providers/__tests__/UnifiedAuthProvider.test.tsx +10 -10
- package/src/providers/services/AuthServiceProvider.tsx +18 -0
- package/src/providers/services/EventServiceProvider.tsx +18 -0
- package/src/providers/services/InactivityServiceProvider.tsx +18 -0
- package/src/providers/services/OrganisationServiceProvider.tsx +18 -0
- package/src/providers/services/UnifiedAuthProvider.tsx +58 -22
- package/src/providers/services/__tests__/AuthServiceProvider.integration.test.tsx +33 -7
- package/src/rbac/README.md +1 -1
- package/src/rbac/__tests__/adapters.comprehensive.test.tsx +26 -26
- package/src/rbac/__tests__/scenarios.user-role.test.tsx +4 -5
- package/src/rbac/adapters.tsx +14 -5
- package/src/rbac/api.ts +100 -67
- package/src/rbac/components/EnhancedNavigationMenu.tsx +1 -1
- package/src/rbac/components/NavigationGuard.tsx +1 -1
- package/src/rbac/components/NavigationProvider.tsx +5 -2
- package/src/rbac/components/PagePermissionGuard.tsx +158 -18
- package/src/rbac/components/PagePermissionProvider.tsx +1 -1
- package/src/rbac/components/PermissionEnforcer.tsx +1 -1
- package/src/rbac/components/RoleBasedRouter.tsx +6 -2
- package/src/rbac/components/SecureDataProvider.test.tsx +84 -49
- package/src/rbac/components/SecureDataProvider.tsx +21 -6
- package/src/rbac/components/__tests__/PagePermissionGuard.race-condition.test.tsx +24 -14
- package/src/rbac/components/__tests__/PagePermissionGuard.test.tsx +7 -0
- package/src/rbac/components/__tests__/PagePermissionGuard.verification.test.tsx +14 -6
- package/src/rbac/components/__tests__/RoleBasedRouter.test.tsx +15 -4
- package/src/rbac/components/__tests__/SecureDataProvider.fixed.test.tsx +148 -24
- package/src/rbac/components/__tests__/SecureDataProvider.test.tsx +81 -15
- package/src/rbac/engine.ts +38 -14
- package/src/rbac/hooks/permissions/index.ts +7 -0
- package/src/rbac/hooks/permissions/useAccessLevel.ts +105 -0
- package/src/rbac/hooks/permissions/useCachedPermissions.ts +79 -0
- package/src/rbac/hooks/permissions/useCan.ts +347 -0
- package/src/rbac/hooks/permissions/useHasAllPermissions.ts +90 -0
- package/src/rbac/hooks/permissions/useHasAnyPermission.ts +90 -0
- package/src/rbac/hooks/permissions/useMultiplePermissions.ts +93 -0
- package/src/rbac/hooks/permissions/usePermissions.ts +253 -0
- package/src/rbac/hooks/useCan.test.ts +71 -64
- package/src/rbac/hooks/usePermissions.ts +14 -995
- package/src/rbac/hooks/useResourcePermissions.test.ts +54 -18
- package/src/rbac/hooks/useResourcePermissions.ts +14 -4
- package/src/rbac/hooks/useSecureSupabase.ts +33 -13
- package/src/rbac/permissions.ts +0 -30
- package/src/rbac/secureClient.ts +212 -61
- package/src/rbac/types.ts +8 -0
- package/src/theming/__tests__/parseEventColours.test.ts +6 -9
- package/src/theming/parseEventColours.ts +5 -19
- package/src/types/vitest-globals.d.ts +51 -26
- package/src/utils/__mocks__/supabaseMock.ts +1 -3
- package/src/utils/__tests__/formatting.unit.test.ts +4 -4
- package/src/utils/__tests__/index.unit.test.ts +2 -2
- package/src/utils/audit/audit.ts +0 -3
- package/src/utils/core/cn.ts +1 -1
- package/src/utils/file-reference/index.ts +53 -1
- package/src/utils/formatting/formatting.ts +8 -18
- package/src/utils/index.ts +0 -1
- package/src/utils/security/secureDataAccess.test.ts +31 -20
- package/src/utils/security/secureDataAccess.ts +4 -3
- package/dist/chunk-6C4YBBJM.js +0 -628
- package/dist/chunk-6C4YBBJM.js.map +0 -1
- package/dist/chunk-7D4SUZUM.js 2.map +0 -1
- package/dist/chunk-7EQTDTTJ.js 2.map +0 -1
- package/dist/chunk-7EQTDTTJ.js.map +0 -1
- package/dist/chunk-7FLMSG37.js 2.map +0 -1
- package/dist/chunk-7FLMSG37.js.map +0 -1
- package/dist/chunk-BC4IJKSL.js.map +0 -1
- package/dist/chunk-E3SPN4VZ.js +0 -12917
- package/dist/chunk-E3SPN4VZ.js.map +0 -1
- package/dist/chunk-E66EQZE6 5.js +0 -37
- package/dist/chunk-E66EQZE6.js 2.map +0 -1
- package/dist/chunk-HWIIPPNI.js.map +0 -1
- package/dist/chunk-I7PSE6JW 5.js +0 -191
- package/dist/chunk-I7PSE6JW.js 2.map +0 -1
- package/dist/chunk-I7PSE6JW.js.map +0 -1
- package/dist/chunk-IIELH4DL.js.map +0 -1
- package/dist/chunk-KNC55RTG.js 5.map +0 -1
- package/dist/chunk-KNC55RTG.js.map +0 -1
- package/dist/chunk-KQCRWDSA.js 5.map +0 -1
- package/dist/chunk-LFNCN2SP.js +0 -412
- package/dist/chunk-LFNCN2SP.js 2.map +0 -1
- package/dist/chunk-LFNCN2SP.js.map +0 -1
- package/dist/chunk-LMC26NLJ 2.js +0 -84
- package/dist/chunk-NOAYCWCX.js +0 -4993
- package/dist/chunk-NOAYCWCX.js.map +0 -1
- package/dist/chunk-QWWZ5CAQ.js 3.map +0 -1
- package/dist/chunk-QWWZ5CAQ.js.map +0 -1
- package/dist/chunk-QXHPKYJV 3.js +0 -113
- package/dist/chunk-R77UEZ4E.js +0 -68
- package/dist/chunk-R77UEZ4E.js.map +0 -1
- package/dist/chunk-SQGMNID3.js.map +0 -1
- package/dist/chunk-VBXEHIUJ.js 6.map +0 -1
- package/dist/chunk-XNXXZ43G.js.map +0 -1
- package/dist/chunk-ZSAAAMVR 6.js +0 -25
- package/dist/components.js 5.map +0 -1
- package/dist/styles/index 2.js +0 -12
- package/dist/styles/index.js 5.map +0 -1
- package/dist/theming/runtime 5.js +0 -19
- package/dist/theming/runtime.js 5.map +0 -1
- package/docs/api/classes/ErrorBoundary.md +0 -144
- package/docs/migration/quick-migration-guide.md +0 -356
- package/docs/migration/service-architecture.md +0 -281
- package/src/hooks/__tests__/useSecureDataAccess.unit.test.tsx +0 -680
- package/src/hooks/useSecureDataAccess.test.ts +0 -559
- package/src/hooks/useSecureDataAccess.ts +0 -666
- /package/dist/{DataTable-5FU7IESH.js.map → DataTable-TPTKCX4D.js.map} +0 -0
- /package/dist/{UnifiedAuthProvider-RGJTDE2C.js.map → UnifiedAuthProvider-CH6Z342H.js.map} +0 -0
- /package/dist/{api-N774RPUA.js.map → api-MVVQZLJI.js.map} +0 -0
- /package/docs/migration/{organisation-context-timing-fix.md → V0.3.44_organisation-context-timing-fix.md} +0 -0
- /package/docs/migration/{rbac-migration.md → V0.4.0_rbac-migration.md} +0 -0
- /package/docs/migration/{person-scoped-profiles-migration-guide.md → V0.5.190_person-scoped-profiles-migration-guide.md} +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
|
@@ -1,51 +1,33 @@
|
|
|
1
1
|
#!/usr/bin/env node
|
|
2
2
|
|
|
3
3
|
/**
|
|
4
|
-
*
|
|
4
|
+
* pace-core Compliance Check Module
|
|
5
5
|
* @package @jmruthers/pace-core
|
|
6
|
-
* @module
|
|
6
|
+
* @module Audit/Checks/Compliance
|
|
7
7
|
*
|
|
8
|
-
* Scans
|
|
9
|
-
*
|
|
8
|
+
* Scans codebase for pace-core compliance violations including:
|
|
9
|
+
* - Restricted imports
|
|
10
|
+
* - Duplicate components/hooks/utils
|
|
11
|
+
* - Custom auth/RBAC code
|
|
12
|
+
* - Provider setup issues
|
|
13
|
+
* - Direct Supabase usage
|
|
14
|
+
* - Unnecessary wrappers
|
|
15
|
+
* - App discovery issues
|
|
10
16
|
*/
|
|
11
17
|
|
|
12
18
|
const fs = require('fs');
|
|
13
19
|
const path = require('path');
|
|
14
|
-
|
|
15
|
-
// ANSI color codes for terminal output
|
|
16
|
-
const colors = {
|
|
17
|
-
reset: '\x1b[0m',
|
|
18
|
-
red: '\x1b[31m',
|
|
19
|
-
green: '\x1b[32m',
|
|
20
|
-
yellow: '\x1b[33m',
|
|
21
|
-
blue: '\x1b[34m',
|
|
22
|
-
cyan: '\x1b[36m',
|
|
23
|
-
bold: '\x1b[1m'
|
|
24
|
-
};
|
|
20
|
+
const { getLineNumber, getRelativePath } = require('../utils.cjs');
|
|
25
21
|
|
|
26
22
|
// Load manifest
|
|
27
23
|
function loadManifest() {
|
|
28
|
-
const manifestPath = path.join(__dirname, '
|
|
24
|
+
const manifestPath = path.join(__dirname, '../../../../core-usage-manifest.json');
|
|
29
25
|
if (!fs.existsSync(manifestPath)) {
|
|
30
|
-
|
|
31
|
-
process.exit(1);
|
|
26
|
+
throw new Error(`core-usage-manifest.json not found at ${manifestPath}`);
|
|
32
27
|
}
|
|
33
28
|
return JSON.parse(fs.readFileSync(manifestPath, 'utf8'));
|
|
34
29
|
}
|
|
35
30
|
|
|
36
|
-
// Find project root (look for package.json, going up from current dir or script location)
|
|
37
|
-
function findProjectRoot(startDir = process.cwd()) {
|
|
38
|
-
let current = path.resolve(startDir);
|
|
39
|
-
while (current !== path.dirname(current)) {
|
|
40
|
-
if (fs.existsSync(path.join(current, 'package.json'))) {
|
|
41
|
-
return current;
|
|
42
|
-
}
|
|
43
|
-
current = path.dirname(current);
|
|
44
|
-
}
|
|
45
|
-
return startDir;
|
|
46
|
-
}
|
|
47
|
-
|
|
48
|
-
// Scan provider setup in main entry files
|
|
49
31
|
function scanProviderSetup(filePath, content, relativePath) {
|
|
50
32
|
const issues = [];
|
|
51
33
|
|
|
@@ -363,8 +345,114 @@ function scanRouterSetup(filePath, content, relativePath) {
|
|
|
363
345
|
return issues;
|
|
364
346
|
}
|
|
365
347
|
|
|
348
|
+
// Helper function to provide migration recommendations
|
|
349
|
+
function getMigrationRecommendation(method, operation) {
|
|
350
|
+
const recommendations = {
|
|
351
|
+
secureQuery: `Replace with: const supabase = useSecureSupabase(); const { data } = await supabase.from('table').select('*');`,
|
|
352
|
+
secureInsert: `Replace with: const supabase = useSecureSupabase(); const { data } = await supabase.from('table').insert(data).select().single();`,
|
|
353
|
+
secureUpdate: `Replace with: const supabase = useSecureSupabase(); const { data } = await supabase.from('table').update(data).eq('id', id).select().single();`,
|
|
354
|
+
secureDelete: `Replace with: const supabase = useSecureSupabase(); await supabase.from('table').delete().eq('id', id);`,
|
|
355
|
+
secureRpc: `Replace with: const supabase = useSecureSupabase(); const { data } = await supabase.rpc('function_name', params);`
|
|
356
|
+
};
|
|
357
|
+
|
|
358
|
+
return recommendations[method] || `Replace with useSecureSupabase() and use standard Supabase ${operation} API`;
|
|
359
|
+
}
|
|
360
|
+
|
|
361
|
+
// Scan for unnecessary wrappers around pace-core components and local components
|
|
362
|
+
function scanUnnecessaryWrappers(content, relativePath, manifest) {
|
|
363
|
+
const issues = [];
|
|
364
|
+
|
|
365
|
+
// Check if file imports from pace-core
|
|
366
|
+
const paceCoreImportPattern = /import\s+{([^}]+)}\s+from\s+['"]@jmruthers\/pace-core['"]/;
|
|
367
|
+
const paceCoreImportMatch = content.match(paceCoreImportPattern);
|
|
368
|
+
|
|
369
|
+
// Extract imported pace-core component names
|
|
370
|
+
let importedPaceCoreComponents = [];
|
|
371
|
+
if (paceCoreImportMatch) {
|
|
372
|
+
importedPaceCoreComponents = (paceCoreImportMatch[1] || '')
|
|
373
|
+
.split(',')
|
|
374
|
+
.map(name => name.trim().replace(/\s+as\s+\w+/, '')) // Remove aliases
|
|
375
|
+
.filter(name => manifest.components.includes(name));
|
|
376
|
+
}
|
|
377
|
+
|
|
378
|
+
// Find exported component definitions
|
|
379
|
+
const componentPattern = /export\s+(default\s+)?(function|const)\s+(\w+)\s*[=\(]/g;
|
|
380
|
+
const componentMatches = [...content.matchAll(componentPattern)];
|
|
381
|
+
|
|
382
|
+
componentMatches.forEach(match => {
|
|
383
|
+
const componentName = match[3];
|
|
384
|
+
const matchIndex = match.index;
|
|
385
|
+
|
|
386
|
+
// Skip if it's a test file or example file
|
|
387
|
+
if (relativePath.includes('.test.') || relativePath.includes('.spec.') ||
|
|
388
|
+
relativePath.includes('example') || relativePath.includes('Example')) {
|
|
389
|
+
return;
|
|
390
|
+
}
|
|
391
|
+
|
|
392
|
+
// Find the component body (simplified - just check if it's a simple wrapper)
|
|
393
|
+
const afterMatch = content.substring(matchIndex + match[0].length, Math.min(content.length, matchIndex + match[0].length + 500));
|
|
394
|
+
|
|
395
|
+
// Check if body has significant logic
|
|
396
|
+
const hasHooks = /use[A-Z]\w+/.test(afterMatch);
|
|
397
|
+
const hasState = /useState|useReducer|useRef/.test(afterMatch);
|
|
398
|
+
const hasConditionals = /if\s*\(|&&|\?|switch/.test(afterMatch);
|
|
399
|
+
const hasMultipleReturns = (afterMatch.match(/return/g) || []).length > 1;
|
|
400
|
+
const hasLoops = /for\s*\(|while\s*\(|\.map\s*\(/.test(afterMatch);
|
|
401
|
+
|
|
402
|
+
// Find JSX components used
|
|
403
|
+
const jsxComponentPattern = /<([A-Z][a-zA-Z0-9]*)[\s>\/]/g;
|
|
404
|
+
const jsxComponents = [];
|
|
405
|
+
let jsxMatch;
|
|
406
|
+
while ((jsxMatch = jsxComponentPattern.exec(afterMatch)) !== null) {
|
|
407
|
+
const jsxComponentName = jsxMatch[1];
|
|
408
|
+
if (jsxComponentName !== 'Fragment' &&
|
|
409
|
+
jsxComponentName !== componentName &&
|
|
410
|
+
!jsxComponents.includes(jsxComponentName)) {
|
|
411
|
+
jsxComponents.push(jsxComponentName);
|
|
412
|
+
}
|
|
413
|
+
}
|
|
414
|
+
|
|
415
|
+
// Check if it's a simple wrapper
|
|
416
|
+
if (jsxComponents.length === 1) {
|
|
417
|
+
const wrappedComponent = jsxComponents[0];
|
|
418
|
+
const wrappedComponentCount = (afterMatch.match(new RegExp(`<${wrappedComponent}`, 'gi')) || []).length;
|
|
419
|
+
|
|
420
|
+
const isSimpleWrapper =
|
|
421
|
+
wrappedComponentCount <= 2 &&
|
|
422
|
+
!hasState &&
|
|
423
|
+
!hasLoops &&
|
|
424
|
+
(!hasMultipleReturns || (hasMultipleReturns && !hasConditionals)) &&
|
|
425
|
+
(!hasHooks || /use(UnifiedAuth|Permissions|Can|RBAC)/.test(afterMatch));
|
|
426
|
+
|
|
427
|
+
if (isSimpleWrapper) {
|
|
428
|
+
const isPaceCoreComponent = importedPaceCoreComponents.includes(wrappedComponent);
|
|
429
|
+
|
|
430
|
+
let reason, recommendation;
|
|
431
|
+
if (isPaceCoreComponent) {
|
|
432
|
+
reason = `Component '${componentName}' appears to be an unnecessary wrapper around pace-core's '${wrappedComponent}'.`;
|
|
433
|
+
recommendation = `Use '${wrappedComponent}' directly from '@jmruthers/pace-core' instead.`;
|
|
434
|
+
} else {
|
|
435
|
+
reason = `Component '${componentName}' appears to be an unnecessary wrapper around '${wrappedComponent}'.`;
|
|
436
|
+
recommendation = `Remove the wrapper and use '${wrappedComponent}' directly.`;
|
|
437
|
+
}
|
|
438
|
+
|
|
439
|
+
issues.push({
|
|
440
|
+
component: componentName,
|
|
441
|
+
wrappedComponent: wrappedComponent,
|
|
442
|
+
file: relativePath,
|
|
443
|
+
line: getLineNumber(content, match[0]),
|
|
444
|
+
reason: reason,
|
|
445
|
+
recommendation: recommendation
|
|
446
|
+
});
|
|
447
|
+
}
|
|
448
|
+
}
|
|
449
|
+
});
|
|
450
|
+
|
|
451
|
+
return issues;
|
|
452
|
+
}
|
|
453
|
+
|
|
366
454
|
// Scan file for violations
|
|
367
|
-
function scanFile(filePath, manifest) {
|
|
455
|
+
function scanFile(filePath, manifest, projectRoot) {
|
|
368
456
|
const violations = {
|
|
369
457
|
restrictedImports: [],
|
|
370
458
|
duplicateComponents: [],
|
|
@@ -375,6 +463,8 @@ function scanFile(filePath, manifest) {
|
|
|
375
463
|
duplicateConfig: [],
|
|
376
464
|
unprotectedPages: [],
|
|
377
465
|
directSupabaseAuth: [],
|
|
466
|
+
directSupabaseClient: [], // Direct Supabase client usage instead of useSecureSupabase
|
|
467
|
+
deprecatedSecureDataAccess: [], // Deprecated useSecureDataAccess with secureQuery/secureInsert/etc
|
|
378
468
|
providerSetupIssues: [],
|
|
379
469
|
viteConfigIssues: [],
|
|
380
470
|
routerSetupIssues: [],
|
|
@@ -383,7 +473,36 @@ function scanFile(filePath, manifest) {
|
|
|
383
473
|
};
|
|
384
474
|
|
|
385
475
|
const content = fs.readFileSync(filePath, 'utf8');
|
|
386
|
-
const relativePath =
|
|
476
|
+
const relativePath = getRelativePath(filePath, projectRoot);
|
|
477
|
+
|
|
478
|
+
// Normalize path for cross-platform compatibility (handle both forward and backslash paths)
|
|
479
|
+
const normalizedPath = relativePath.replace(/\\/g, '/');
|
|
480
|
+
|
|
481
|
+
// Skip Edge Functions - they run in Deno, not React, so React hooks aren't available
|
|
482
|
+
// Direct Supabase auth calls are the correct approach in Edge Functions
|
|
483
|
+
const isEdgeFunction = normalizedPath.includes('supabase/functions/');
|
|
484
|
+
|
|
485
|
+
// Skip pace-core package files - compliance checks are for consuming applications, not the library itself
|
|
486
|
+
// The library must import these dependencies to build its components, and its own components/hooks/utils
|
|
487
|
+
// are the source of truth, not duplicates
|
|
488
|
+
const isPaceCorePackage = normalizedPath.includes('packages/core/') || normalizedPath.startsWith('packages/core/');
|
|
489
|
+
if (isPaceCorePackage) {
|
|
490
|
+
return violations; // Return empty violations for pace-core package files
|
|
491
|
+
}
|
|
492
|
+
|
|
493
|
+
// Skip scripts directory - these are utility/setup scripts, not application code
|
|
494
|
+
// Scripts may legitimately need direct database access for admin operations
|
|
495
|
+
const isScript = normalizedPath.startsWith('scripts/') || normalizedPath.includes('/scripts/');
|
|
496
|
+
if (isScript) {
|
|
497
|
+
return violations; // Return empty violations for script files
|
|
498
|
+
}
|
|
499
|
+
|
|
500
|
+
// Skip root-level src directory - in pace-core repository, this is a demo/showcase app, not a consuming app
|
|
501
|
+
// The audit is designed for consuming applications, not demo apps in the library repository
|
|
502
|
+
const isRootSrc = normalizedPath.startsWith('src/') && !normalizedPath.includes('packages/');
|
|
503
|
+
if (isRootSrc) {
|
|
504
|
+
return violations; // Return empty violations for root-level src files (demo apps)
|
|
505
|
+
}
|
|
387
506
|
|
|
388
507
|
// Check for restricted imports
|
|
389
508
|
manifest.restrictedImports.forEach(({ module, reason }) => {
|
|
@@ -478,6 +597,11 @@ function scanFile(filePath, manifest) {
|
|
|
478
597
|
// RBAC/Auth Compliance Checks
|
|
479
598
|
// ============================================
|
|
480
599
|
|
|
600
|
+
// Check if file imports from pace-core for auth/rbac (define at function scope)
|
|
601
|
+
const hasPaceCoreImport = /from\s+['"]@jmruthers\/pace-core/.test(content) ||
|
|
602
|
+
/from\s+['"]@jmruthers\/pace-core\/rbac/.test(content) ||
|
|
603
|
+
/from\s+['"]@jmruthers\/pace-core\/providers/.test(content);
|
|
604
|
+
|
|
481
605
|
// Check for custom auth/rbac/permission code that doesn't import from pace-core
|
|
482
606
|
const authRbacPatterns = [
|
|
483
607
|
// Custom auth hooks
|
|
@@ -508,11 +632,6 @@ function scanFile(filePath, manifest) {
|
|
|
508
632
|
{ pattern: /export\s+(default\s+)?(function|const)\s+isPermitted\s*[=\(]/g, name: 'isPermitted', type: 'util' }
|
|
509
633
|
];
|
|
510
634
|
|
|
511
|
-
// Check if file imports from pace-core for auth/rbac
|
|
512
|
-
const hasPaceCoreImport = /from\s+['"]@jmruthers\/pace-core/.test(content) ||
|
|
513
|
-
/from\s+['"]@jmruthers\/pace-core\/rbac/.test(content) ||
|
|
514
|
-
/from\s+['"]@jmruthers\/pace-core\/providers/.test(content);
|
|
515
|
-
|
|
516
635
|
authRbacPatterns.forEach(({ pattern, name, type, replacement }) => {
|
|
517
636
|
// Create a new regex instance to avoid state issues
|
|
518
637
|
const testPattern = new RegExp(pattern.source, pattern.flags);
|
|
@@ -530,7 +649,7 @@ function scanFile(filePath, manifest) {
|
|
|
530
649
|
].includes(name);
|
|
531
650
|
|
|
532
651
|
// Check if the hook uses pace-core RBAC APIs (use a fresh regex to avoid state issues)
|
|
533
|
-
const rbacApiPattern = /useRoleManagement|useSecureSupabase|
|
|
652
|
+
const rbacApiPattern = /useRoleManagement|useSecureSupabase|useRBAC|usePermissions|useCan|rbac_role_grant|rbac_role_revoke|rbac_roles_list|data_rbac_apps_list/;
|
|
534
653
|
const usesPaceCoreRBAC = isCustomRBACHook && rbacApiPattern.test(content);
|
|
535
654
|
|
|
536
655
|
// Check for @pace-core-compliant comment (use a fresh regex)
|
|
@@ -660,9 +779,22 @@ function scanFile(filePath, manifest) {
|
|
|
660
779
|
|
|
661
780
|
// Skip Edge Functions - they run in Deno, not React, so React hooks are not available
|
|
662
781
|
// Edge Functions MUST use direct supabase.auth.getUser() calls - this is correct and required
|
|
663
|
-
//
|
|
664
|
-
|
|
665
|
-
|
|
782
|
+
// isEdgeFunction is already defined above
|
|
783
|
+
|
|
784
|
+
// Other auth patterns - defined here so it's accessible in all scopes
|
|
785
|
+
const otherAuthPatterns = [
|
|
786
|
+
{ pattern: /\.auth\.signIn\s*\(/g, method: 'signIn' },
|
|
787
|
+
{ pattern: /\.auth\.signUp\s*\(/g, method: 'signUp' },
|
|
788
|
+
{ pattern: /\.auth\.signOut\s*\(/g, method: 'signOut' },
|
|
789
|
+
{ pattern: /\.auth\.onAuthStateChange\s*\(/g, method: 'onAuthStateChange' }
|
|
790
|
+
];
|
|
791
|
+
|
|
792
|
+
// Check if file actually uses useUnifiedAuth hook (not just imports it)
|
|
793
|
+
// Define these at function scope so they're available throughout
|
|
794
|
+
const usesUnifiedAuthHook = /useUnifiedAuth\s*\(/.test(content);
|
|
795
|
+
const hasUnifiedAuthImport = /UnifiedAuthProvider/.test(content) ||
|
|
796
|
+
/useUnifiedAuth/.test(content) ||
|
|
797
|
+
/from\s+['"]@jmruthers\/pace-core\/providers/.test(content);
|
|
666
798
|
|
|
667
799
|
// Skip all auth checks for Edge Functions - they cannot use React hooks
|
|
668
800
|
if (isEdgeFunction) {
|
|
@@ -683,20 +815,6 @@ function scanFile(filePath, manifest) {
|
|
|
683
815
|
{ pattern: /\w+\.auth\.getUser\s*\(/g, method: 'getUser', specific: false },
|
|
684
816
|
{ pattern: /\w+\.auth\.getSession\s*\(/g, method: 'getSession', specific: false }
|
|
685
817
|
];
|
|
686
|
-
|
|
687
|
-
// Other auth patterns
|
|
688
|
-
const otherAuthPatterns = [
|
|
689
|
-
{ pattern: /\.auth\.signIn\s*\(/g, method: 'signIn' },
|
|
690
|
-
{ pattern: /\.auth\.signUp\s*\(/g, method: 'signUp' },
|
|
691
|
-
{ pattern: /\.auth\.signOut\s*\(/g, method: 'signOut' },
|
|
692
|
-
{ pattern: /\.auth\.onAuthStateChange\s*\(/g, method: 'onAuthStateChange' }
|
|
693
|
-
];
|
|
694
|
-
|
|
695
|
-
// Check if file actually uses useUnifiedAuth hook (not just imports it)
|
|
696
|
-
const usesUnifiedAuthHook = /useUnifiedAuth\s*\(/.test(content);
|
|
697
|
-
const hasUnifiedAuthImport = /UnifiedAuthProvider/.test(content) ||
|
|
698
|
-
/useUnifiedAuth/.test(content) ||
|
|
699
|
-
/from\s+['"]@jmruthers\/pace-core\/providers/.test(content);
|
|
700
818
|
|
|
701
819
|
// Check for usage of useCurrentUser hook (even if imported from local file)
|
|
702
820
|
// This catches both local imports and direct usage
|
|
@@ -760,7 +878,8 @@ function scanFile(filePath, manifest) {
|
|
|
760
878
|
const isInLineComment = /\/\/[^\n]*$/.test(lineUpToMatch);
|
|
761
879
|
|
|
762
880
|
// Only skip if clearly in a comment - be conservative
|
|
763
|
-
|
|
881
|
+
// Also skip Edge Functions - they run in Deno, not React, so React hooks aren't available
|
|
882
|
+
if (!isInLineComment && !isEdgeFunction) {
|
|
764
883
|
violations.directSupabaseAuth.push({
|
|
765
884
|
file: relativePath,
|
|
766
885
|
line: getLineNumber(content, matchText),
|
|
@@ -794,7 +913,8 @@ function scanFile(filePath, manifest) {
|
|
|
794
913
|
const lineUpToMatch = content.substring(lineStart, searchIndex);
|
|
795
914
|
const isInLineComment = /\/\/[^\n]*$/.test(lineUpToMatch);
|
|
796
915
|
|
|
797
|
-
|
|
916
|
+
// Skip Edge Functions - they run in Deno, not React, so React hooks aren't available
|
|
917
|
+
if (!isInLineComment && !isEdgeFunction) {
|
|
798
918
|
// Calculate line number from index
|
|
799
919
|
const lineNum = content.substring(0, searchIndex).split('\n').length;
|
|
800
920
|
|
|
@@ -855,7 +975,8 @@ function scanFile(filePath, manifest) {
|
|
|
855
975
|
(doubleQuotes % 2 === 1 && beforeMatch.endsWith('"')) ||
|
|
856
976
|
(backticks % 2 === 1 && beforeMatch.endsWith('`'));
|
|
857
977
|
|
|
858
|
-
|
|
978
|
+
// Skip Edge Functions - they run in Deno, not React, so React hooks aren't available
|
|
979
|
+
if (!isInLineComment && !isInBlockComment && !isInString && !usesUnifiedAuthHook && !isEdgeFunction) {
|
|
859
980
|
violations.directSupabaseAuth.push({
|
|
860
981
|
file: relativePath,
|
|
861
982
|
line: getLineNumber(content, matchText),
|
|
@@ -877,11 +998,11 @@ function scanFile(filePath, manifest) {
|
|
|
877
998
|
{ name: 'rbac_apps', type: 'config', recommendation: 'For admin operations, use useSecureSupabase. For application use, use pace-core RBAC APIs' },
|
|
878
999
|
{ name: 'rbac_app_pages', type: 'config', recommendation: 'For admin operations, use useSecureSupabase. For application use, use pace-core permission management APIs or PagePermissionGuard' },
|
|
879
1000
|
{ name: 'rbac_page_permissions', type: 'config', recommendation: 'For admin operations, use useSecureSupabase. For application use, use pace-core permission management APIs or PagePermissionGuard' },
|
|
880
|
-
{ name: 'rbac_user_units', type: 'user_data', recommendation: 'Use useSecureSupabase
|
|
1001
|
+
{ name: 'rbac_user_units', type: 'user_data', recommendation: 'Use useSecureSupabase. For reading, consider data_user_unit_get RPC function' },
|
|
881
1002
|
// User data tables - acceptable to query but must use secure methods
|
|
882
|
-
{ name: 'rbac_user_profiles', type: 'user_data', recommendation: 'Use useSecureSupabase
|
|
883
|
-
{ name: 'rbac_user_login_history', type: 'audit', recommendation: 'Use useSecureSupabase
|
|
884
|
-
{ name: 'rbac_user_sessions', type: 'session', recommendation: 'Use useSecureSupabase
|
|
1003
|
+
{ name: 'rbac_user_profiles', type: 'user_data', recommendation: 'Use useSecureSupabase from pace-core to ensure organisation context is enforced' },
|
|
1004
|
+
{ name: 'rbac_user_login_history', type: 'audit', recommendation: 'Use useSecureSupabase from pace-core. Login history is automatically tracked by UnifiedAuthProvider, but queries should use secure methods' },
|
|
1005
|
+
{ name: 'rbac_user_sessions', type: 'session', recommendation: 'Use useSecureSupabase from pace-core to ensure organisation context is enforced' }
|
|
885
1006
|
];
|
|
886
1007
|
|
|
887
1008
|
// Detect admin/management context
|
|
@@ -906,26 +1027,19 @@ function scanFile(filePath, manifest) {
|
|
|
906
1027
|
/useRBAC/.test(content) ||
|
|
907
1028
|
/usePermissions/.test(content) ||
|
|
908
1029
|
/useSecureSupabase/.test(content) ||
|
|
909
|
-
/useSecureDataAccess/.test(content) ||
|
|
910
1030
|
/PagePermissionGuard/.test(content);
|
|
911
1031
|
|
|
912
|
-
// Check if file
|
|
913
|
-
const usesSecureDataAccess = /useSecureDataAccess/.test(content);
|
|
914
|
-
|
|
915
|
-
// Check if file destructures secure methods from useSecureDataAccess
|
|
1032
|
+
// Check if file destructures secure methods (deprecated secureQuery/secureInsert/etc from useSecureDataAccess)
|
|
916
1033
|
const hasSecureMethods = /(const|let)\s*\{[^}]*secure(Query|Update|Insert|Delete)/.test(content) ||
|
|
917
1034
|
/secure(Query|Update|Insert|Delete)\s*\(/.test(content);
|
|
918
1035
|
|
|
919
1036
|
// First, identify all variables assigned from secure hooks
|
|
920
|
-
// Match patterns like: const supabase = useSecureSupabase();
|
|
1037
|
+
// Match patterns like: const supabase = useSecureSupabase();
|
|
921
1038
|
// Also detect fromSupabaseClient and wrapper patterns
|
|
922
1039
|
const secureVariablePatterns = [
|
|
923
1040
|
/const\s+(\w+)\s*=\s*useSecureSupabase\s*\(/g,
|
|
924
|
-
/const\s+(\w+)\s*=\s*useSecureDataAccess\s*\(/g,
|
|
925
1041
|
/let\s+(\w+)\s*=\s*useSecureSupabase\s*\(/g,
|
|
926
|
-
/let\s+(\w+)\s*=\s*useSecureDataAccess\s*\(/g,
|
|
927
1042
|
/(\w+)\s*=\s*useSecureSupabase\s*\(/g,
|
|
928
|
-
/(\w+)\s*=\s*useSecureDataAccess\s*\(/g,
|
|
929
1043
|
// Detect fromSupabaseClient usage
|
|
930
1044
|
/const\s+(\w+)\s*=\s*fromSupabaseClient\s*\(/g,
|
|
931
1045
|
/let\s+(\w+)\s*=\s*fromSupabaseClient\s*\(/g,
|
|
@@ -1051,12 +1165,55 @@ function scanFile(filePath, manifest) {
|
|
|
1051
1165
|
const isConfigTable = type === 'config';
|
|
1052
1166
|
|
|
1053
1167
|
// Check if the variable comes from a secure hook
|
|
1054
|
-
// Check both the secureVariables set and also check if the variable is assigned from useSecureSupabase
|
|
1168
|
+
// Check both the secureVariables set and also check if the variable is assigned from useSecureSupabase
|
|
1055
1169
|
// Escape special regex characters in variable name and use multiline flag to handle newlines
|
|
1056
1170
|
const escapedVarName = variableName ? variableName.replace(/[.*+?^${}()|[\]\\]/g, '\\$&') : '';
|
|
1057
|
-
|
|
1058
|
-
|
|
1059
|
-
|
|
1171
|
+
|
|
1172
|
+
// Check if variable is declared locally (const/let = useSecureSupabase())
|
|
1173
|
+
const isDeclaredSecure = secureVariables.has(variableName) ||
|
|
1174
|
+
(variableName && new RegExp(`(const|let)\\s+${escapedVarName}\\s*=\\s*useSecureSupabase\\s*\\(`, 'm').test(content));
|
|
1175
|
+
|
|
1176
|
+
// Check if variable is passed as function parameter with secure type annotation
|
|
1177
|
+
// Look for function signatures with type annotations indicating secure client
|
|
1178
|
+
const isParameterSecure = variableName && (
|
|
1179
|
+
// Check in beforeMatch (200 chars before) for parameter type annotations
|
|
1180
|
+
new RegExp(`\\b${escapedVarName}\\s*:\\s*.*(SecureSupabaseClient|useSecureSupabase|ReturnType.*useSecureSupabase)`, 'm').test(beforeMatch) ||
|
|
1181
|
+
// Check full content for function signatures with secure type annotations
|
|
1182
|
+
new RegExp(`(function|=>|async\\s+function|const\\s+\\w+\\s*=\\s*(async\\s+)?(function|=>))[^(]*\\([^)]*\\b${escapedVarName}\\s*:\\s*.*(SecureSupabaseClient|useSecureSupabase|ReturnType.*useSecureSupabase)`, 'm').test(content) ||
|
|
1183
|
+
// Check for ReturnType<typeof import pattern (common in TypeScript)
|
|
1184
|
+
new RegExp(`\\b${escapedVarName}\\s*:\\s*.*ReturnType.*useSecureSupabase`, 'm').test(content) ||
|
|
1185
|
+
// Check for NonNullable<ReturnType<typeof useSecureSupabase>> pattern
|
|
1186
|
+
new RegExp(`\\b${escapedVarName}\\s*:\\s*.*NonNullable.*ReturnType.*useSecureSupabase`, 'm').test(content)
|
|
1187
|
+
);
|
|
1188
|
+
|
|
1189
|
+
// Check for common secure variable names (heuristic for secure usage)
|
|
1190
|
+
const isSecureVariableName = variableName && /^(secureSupabase|secureClient|secureDb|secure)$/i.test(variableName);
|
|
1191
|
+
|
|
1192
|
+
// Check for comments indicating secure usage (pace-core-compliant, useSecureSupabase, etc.)
|
|
1193
|
+
// Also check for @pace-core-compliant annotation which can suppress false positives
|
|
1194
|
+
const hasSecureComment = variableName && (
|
|
1195
|
+
// Check in beforeMatch for comments
|
|
1196
|
+
new RegExp(`(pace-core-compliant|useSecureSupabase|secureSupabase|@pace-core-compliant)`, 'i').test(beforeMatch) ||
|
|
1197
|
+
// Check for comments on previous lines (up to 10 lines back for better coverage)
|
|
1198
|
+
(() => {
|
|
1199
|
+
const lines = content.substring(0, matchIndex).split('\n');
|
|
1200
|
+
const recentLines = lines.slice(Math.max(0, lines.length - 10)).join('\n');
|
|
1201
|
+
return new RegExp(`(pace-core-compliant|useSecureSupabase|secureSupabase|@pace-core-compliant)`, 'i').test(recentLines);
|
|
1202
|
+
})()
|
|
1203
|
+
);
|
|
1204
|
+
|
|
1205
|
+
// Check for @pace-core-compliant annotation on the same line or previous lines (suppression mechanism)
|
|
1206
|
+
const hasComplianceAnnotation = (() => {
|
|
1207
|
+
const lines = content.substring(0, matchIndex).split('\n');
|
|
1208
|
+
const currentLineIdx = lines.length - 1;
|
|
1209
|
+
// Check current line and up to 3 previous lines for @pace-core-compliant
|
|
1210
|
+
const checkLines = lines.slice(Math.max(0, currentLineIdx - 3), currentLineIdx + 1);
|
|
1211
|
+
return new RegExp(`@pace-core-compliant`, 'i').test(checkLines.join('\n'));
|
|
1212
|
+
})();
|
|
1213
|
+
|
|
1214
|
+
// Combine all checks
|
|
1215
|
+
// If @pace-core-compliant annotation is present, trust it (suppression mechanism)
|
|
1216
|
+
const isUsingSecureVariable = hasComplianceAnnotation || isDeclaredSecure || isParameterSecure || (isSecureVariableName && hasSecureComment);
|
|
1060
1217
|
|
|
1061
1218
|
// Determine severity based on context
|
|
1062
1219
|
let severity = 'error';
|
|
@@ -1068,7 +1225,7 @@ function scanFile(filePath, manifest) {
|
|
|
1068
1225
|
continue;
|
|
1069
1226
|
} else {
|
|
1070
1227
|
severity = 'error';
|
|
1071
|
-
reason = `Direct query to RBAC table '${tableName}' detected. This table can be queried, but you MUST use useSecureSupabase
|
|
1228
|
+
reason = `Direct query to RBAC table '${tableName}' detected. This table can be queried, but you MUST use useSecureSupabase to ensure organisation context is enforced and RLS policies are respected.`;
|
|
1072
1229
|
}
|
|
1073
1230
|
} else if (isConfigTable) {
|
|
1074
1231
|
if (isUsingSecureVariable) {
|
|
@@ -1078,7 +1235,7 @@ function scanFile(filePath, manifest) {
|
|
|
1078
1235
|
} else if (isAdminContext) {
|
|
1079
1236
|
// Admin operations without secure methods - warning
|
|
1080
1237
|
severity = 'warning';
|
|
1081
|
-
reason = `Admin operation on configuration table '${tableName}' detected. Ensure you're using useSecureSupabase
|
|
1238
|
+
reason = `Admin operation on configuration table '${tableName}' detected. Ensure you're using useSecureSupabase for security.`;
|
|
1082
1239
|
} else {
|
|
1083
1240
|
// Not admin context and not using secure methods - error
|
|
1084
1241
|
severity = 'error';
|
|
@@ -1140,8 +1297,8 @@ function scanFile(filePath, manifest) {
|
|
|
1140
1297
|
} else if (isConfigTable) {
|
|
1141
1298
|
// Config tables using secure methods - acceptable for admin operations
|
|
1142
1299
|
// If using secureQuery/secureUpdate/etc., it's already using secure methods
|
|
1143
|
-
if (isAdminContext
|
|
1144
|
-
// Config tables in admin context
|
|
1300
|
+
if (isAdminContext) {
|
|
1301
|
+
// Config tables in admin context - acceptable
|
|
1145
1302
|
continue; // Skip - this is correct usage for admin operations
|
|
1146
1303
|
} else {
|
|
1147
1304
|
severity = 'error';
|
|
@@ -1217,10 +1374,9 @@ function scanFile(filePath, manifest) {
|
|
|
1217
1374
|
// Check if using secure variable (check both set and direct pattern match)
|
|
1218
1375
|
// Escape special regex characters in variable name and use multiline flag to handle newlines
|
|
1219
1376
|
const escapedVarName = variableName ? variableName.replace(/[.*+?^${}()|[\]\\]/g, '\\$&') : '';
|
|
1220
|
-
// Check if variable is declared with useSecureSupabase
|
|
1377
|
+
// Check if variable is declared with useSecureSupabase
|
|
1221
1378
|
const isDeclaredSecure = (variableName && secureVariables.has(variableName)) ||
|
|
1222
|
-
(variableName && new RegExp(`(const|let)\\s+${escapedVarName}\\s*=\\s*useSecureSupabase\\s*\\(`, 'm').test(content))
|
|
1223
|
-
(variableName && new RegExp(`(const|let)\\s+${escapedVarName}\\s*=\\s*useSecureDataAccess\\s*\\(`, 'm').test(content));
|
|
1379
|
+
(variableName && new RegExp(`(const|let)\\s+${escapedVarName}\\s*=\\s*useSecureSupabase\\s*\\(`, 'm').test(content));
|
|
1224
1380
|
// Check if variable is passed as parameter with useSecureSupabase type annotation
|
|
1225
1381
|
// Look for the parameter in function signatures (check both beforeMatch and full content)
|
|
1226
1382
|
const isParameterSecure = variableName && (
|
|
@@ -1262,12 +1418,12 @@ function scanFile(filePath, manifest) {
|
|
|
1262
1418
|
let severity = 'error';
|
|
1263
1419
|
let reason = '';
|
|
1264
1420
|
|
|
1265
|
-
if (isConfigTable &&
|
|
1421
|
+
if (isConfigTable && isAdminContext && (isUsingSecureVariable || hasSecureMethods)) {
|
|
1266
1422
|
// Admin operations with secure methods - acceptable, skip
|
|
1267
1423
|
continue;
|
|
1268
1424
|
} else if (isConfigTable && isAdminContext && !isUsingSecureVariable && !hasSecureMethods) {
|
|
1269
1425
|
severity = 'warning';
|
|
1270
|
-
reason = `Admin operation on configuration table '${tableName}' detected. Ensure you're using useSecureSupabase
|
|
1426
|
+
reason = `Admin operation on configuration table '${tableName}' detected. Ensure you're using useSecureSupabase for security.`;
|
|
1271
1427
|
} else if (isConfigTable && !isAdminContext) {
|
|
1272
1428
|
severity = 'error';
|
|
1273
1429
|
reason = `Direct query to RBAC configuration table '${tableName}' detected. These are system configuration tables. For admin operations, use useSecureSupabase. For application use, use pace-core RBAC APIs.`;
|
|
@@ -1277,7 +1433,7 @@ function scanFile(filePath, manifest) {
|
|
|
1277
1433
|
continue;
|
|
1278
1434
|
}
|
|
1279
1435
|
severity = 'error';
|
|
1280
|
-
reason = `Direct query to RBAC table '${tableName}' detected. This table can be queried, but you MUST use useSecureSupabase
|
|
1436
|
+
reason = `Direct query to RBAC table '${tableName}' detected. This table can be queried, but you MUST use useSecureSupabase to ensure organisation context is enforced and RLS policies are respected.`;
|
|
1281
1437
|
} else {
|
|
1282
1438
|
severity = 'error';
|
|
1283
1439
|
reason = `Direct query to RBAC table '${tableName}' detected. Use pace-core RBAC hooks, RPC functions, or useSecureSupabase instead.`;
|
|
@@ -1462,6 +1618,61 @@ function scanFile(filePath, manifest) {
|
|
|
1462
1618
|
// For .from() patterns, match[1] is the CRUD method; for secure* patterns, operation is already set
|
|
1463
1619
|
const crudMethod = method === 'from' ? match[1] : operation;
|
|
1464
1620
|
|
|
1621
|
+
// Extract variable name for .from() patterns (for secure* patterns, we skip variable detection)
|
|
1622
|
+
let variableName = null;
|
|
1623
|
+
if (method === 'from') {
|
|
1624
|
+
// Look backwards to find the variable name before .from()
|
|
1625
|
+
const beforeMatch = content.substring(Math.max(0, matchIndex - 200), matchIndex);
|
|
1626
|
+
const parts = beforeMatch.split('.from');
|
|
1627
|
+
if (parts.length > 0) {
|
|
1628
|
+
const beforeFrom = parts[parts.length - 1].trim();
|
|
1629
|
+
const words = beforeFrom.match(/\b\w+\b/g);
|
|
1630
|
+
if (words && words.length > 0) {
|
|
1631
|
+
variableName = words[words.length - 1];
|
|
1632
|
+
}
|
|
1633
|
+
}
|
|
1634
|
+
}
|
|
1635
|
+
|
|
1636
|
+
// Check if using secure variable (only for .from() patterns)
|
|
1637
|
+
let isUsingSecureVariable = false;
|
|
1638
|
+
if (method === 'from' && variableName) {
|
|
1639
|
+
const escapedVarName = variableName.replace(/[.*+?^${}()|[\]\\]/g, '\\$&');
|
|
1640
|
+
|
|
1641
|
+
// Check if variable is declared locally
|
|
1642
|
+
const isDeclaredSecure = secureVariables.has(variableName) ||
|
|
1643
|
+
new RegExp(`(const|let)\\s+${escapedVarName}\\s*=\\s*useSecureSupabase\\s*\\(`, 'm').test(content);
|
|
1644
|
+
|
|
1645
|
+
// Check if variable is passed as function parameter with secure type annotation
|
|
1646
|
+
const beforeMatch = content.substring(Math.max(0, matchIndex - 200), matchIndex);
|
|
1647
|
+
const isParameterSecure =
|
|
1648
|
+
new RegExp(`\\b${escapedVarName}\\s*:\\s*.*(SecureSupabaseClient|useSecureSupabase|ReturnType.*useSecureSupabase)`, 'm').test(beforeMatch) ||
|
|
1649
|
+
new RegExp(`(function|=>|async\\s+function|const\\s+\\w+\\s*=\\s*(async\\s+)?(function|=>))[^(]*\\([^)]*\\b${escapedVarName}\\s*:\\s*.*(SecureSupabaseClient|useSecureSupabase|ReturnType.*useSecureSupabase)`, 'm').test(content) ||
|
|
1650
|
+
new RegExp(`\\b${escapedVarName}\\s*:\\s*.*ReturnType.*useSecureSupabase`, 'm').test(content) ||
|
|
1651
|
+
new RegExp(`\\b${escapedVarName}\\s*:\\s*.*NonNullable.*ReturnType.*useSecureSupabase`, 'm').test(content);
|
|
1652
|
+
|
|
1653
|
+
// Check for common secure variable names
|
|
1654
|
+
const isSecureVariableName = /^(secureSupabase|secureClient|secureDb|secure)$/i.test(variableName);
|
|
1655
|
+
|
|
1656
|
+
// Check for comments indicating secure usage
|
|
1657
|
+
const hasSecureComment =
|
|
1658
|
+
new RegExp(`(pace-core-compliant|useSecureSupabase|secureSupabase|@pace-core-compliant)`, 'i').test(beforeMatch) ||
|
|
1659
|
+
(() => {
|
|
1660
|
+
const lines = content.substring(0, matchIndex).split('\n');
|
|
1661
|
+
const recentLines = lines.slice(Math.max(0, lines.length - 10)).join('\n');
|
|
1662
|
+
return new RegExp(`(pace-core-compliant|useSecureSupabase|secureSupabase|@pace-core-compliant)`, 'i').test(recentLines);
|
|
1663
|
+
})();
|
|
1664
|
+
|
|
1665
|
+
// Check for @pace-core-compliant annotation (suppression mechanism)
|
|
1666
|
+
const hasComplianceAnnotation = (() => {
|
|
1667
|
+
const lines = content.substring(0, matchIndex).split('\n');
|
|
1668
|
+
const currentLineIdx = lines.length - 1;
|
|
1669
|
+
const checkLines = lines.slice(Math.max(0, currentLineIdx - 3), currentLineIdx + 1);
|
|
1670
|
+
return new RegExp(`@pace-core-compliant`, 'i').test(checkLines.join('\n'));
|
|
1671
|
+
})();
|
|
1672
|
+
|
|
1673
|
+
isUsingSecureVariable = hasComplianceAnnotation || isDeclaredSecure || isParameterSecure || (isSecureVariableName && hasSecureComment);
|
|
1674
|
+
}
|
|
1675
|
+
|
|
1465
1676
|
// Skip if in a line comment
|
|
1466
1677
|
const lineStart = content.lastIndexOf('\n', matchIndex) + 1;
|
|
1467
1678
|
const lineUpToMatch = content.substring(lineStart, matchIndex);
|
|
@@ -1473,6 +1684,16 @@ function scanFile(filePath, manifest) {
|
|
|
1473
1684
|
let severity = 'error';
|
|
1474
1685
|
let example = '';
|
|
1475
1686
|
|
|
1687
|
+
// If using secure variable for user_data tables, skip (correct usage)
|
|
1688
|
+
if (isUserData && isUsingSecureVariable) {
|
|
1689
|
+
continue;
|
|
1690
|
+
}
|
|
1691
|
+
|
|
1692
|
+
// If using secure variable for config tables, skip (correct usage)
|
|
1693
|
+
if (isConfig && isUsingSecureVariable) {
|
|
1694
|
+
continue;
|
|
1695
|
+
}
|
|
1696
|
+
|
|
1476
1697
|
if (isRole) {
|
|
1477
1698
|
// Role mutations should use RPC functions
|
|
1478
1699
|
const isGrant = crudMethod === 'insert' || crudMethod === 'upsert';
|
|
@@ -1495,24 +1716,25 @@ function scanFile(filePath, manifest) {
|
|
|
1495
1716
|
if (method && method.startsWith('secure')) {
|
|
1496
1717
|
// Using secureInsert/secureUpdate/secureDelete - this is correct, don't flag
|
|
1497
1718
|
continue;
|
|
1498
|
-
} else if (isAdminContext
|
|
1499
|
-
// Admin context
|
|
1719
|
+
} else if (isAdminContext) {
|
|
1720
|
+
// Admin context - check if using secure methods
|
|
1500
1721
|
// If the operation uses secureQuery/secureUpdate/etc, it's already handled above
|
|
1501
1722
|
// This case is for direct .from() calls in admin context
|
|
1502
1723
|
severity = 'warning';
|
|
1503
|
-
reason = `Admin operation (${crudMethod}) on configuration table '${table}' detected. Ensure you're using useSecureSupabase
|
|
1504
|
-
replacement = 'Use useSecureSupabase
|
|
1505
|
-
} else if (isAdminContext) {
|
|
1506
|
-
severity = 'warning';
|
|
1507
|
-
reason = `Admin operation (${crudMethod}) on configuration table '${table}' detected. Ensure you're using useSecureSupabase or useSecureDataAccess for security.`;
|
|
1508
|
-
replacement = 'Use useSecureSupabase or useSecureDataAccess from pace-core for admin operations on configuration tables';
|
|
1724
|
+
reason = `Admin operation (${crudMethod}) on configuration table '${table}' detected. Ensure you're using useSecureSupabase for security.`;
|
|
1725
|
+
replacement = 'Use useSecureSupabase from pace-core for admin operations on configuration tables';
|
|
1509
1726
|
} else {
|
|
1510
1727
|
reason = `Direct ${crudMethod} operation on configuration table '${table}' detected. These are system configuration tables. For admin operations, use useSecureSupabase.`;
|
|
1511
|
-
replacement = 'Use useSecureSupabase
|
|
1728
|
+
replacement = 'Use useSecureSupabase from pace-core for admin operations';
|
|
1512
1729
|
}
|
|
1513
1730
|
} else if (isUserData) {
|
|
1514
|
-
|
|
1515
|
-
|
|
1731
|
+
// User data tables - should use secure methods
|
|
1732
|
+
if (isUsingSecureVariable) {
|
|
1733
|
+
// Already handled above - skip
|
|
1734
|
+
continue;
|
|
1735
|
+
}
|
|
1736
|
+
reason = `Direct ${crudMethod} operation on '${table}' detected. Use useSecureSupabase to ensure organisation context is enforced.`;
|
|
1737
|
+
replacement = 'Use useSecureSupabase from pace-core';
|
|
1516
1738
|
} else {
|
|
1517
1739
|
reason = `Direct ${crudMethod} operation on '${table}' detected. Use pace-core permission management APIs or documented RPC functions instead.`;
|
|
1518
1740
|
replacement = 'pace-core permission management APIs or RPC functions';
|
|
@@ -1621,7 +1843,7 @@ function scanFile(filePath, manifest) {
|
|
|
1621
1843
|
type,
|
|
1622
1844
|
file: relativePath,
|
|
1623
1845
|
line: getLineNumber(content, hookStartMatch[0]),
|
|
1624
|
-
reason: `Custom ${type} '${name}' detected that implements role/permission management logic. Even though it may use some pace-core utilities
|
|
1846
|
+
reason: `Custom ${type} '${name}' detected that implements role/permission management logic. Even though it may use some pace-core utilities, it should use pace-core RBAC APIs instead of implementing custom logic.`,
|
|
1625
1847
|
replacement,
|
|
1626
1848
|
severity: 'error'
|
|
1627
1849
|
});
|
|
@@ -1632,7 +1854,7 @@ function scanFile(filePath, manifest) {
|
|
|
1632
1854
|
type,
|
|
1633
1855
|
file: relativePath,
|
|
1634
1856
|
line: getLineNumber(content, hookStartMatch[0]),
|
|
1635
|
-
reason: `Custom ${type} '${name}' detected that implements role/permission management logic. Even though it may use some pace-core utilities
|
|
1857
|
+
reason: `Custom ${type} '${name}' detected that implements role/permission management logic. Even though it may use some pace-core utilities, it should use pace-core RBAC APIs instead of implementing custom logic.`,
|
|
1636
1858
|
replacement,
|
|
1637
1859
|
severity: 'error'
|
|
1638
1860
|
});
|
|
@@ -1643,7 +1865,7 @@ function scanFile(filePath, manifest) {
|
|
|
1643
1865
|
type,
|
|
1644
1866
|
file: relativePath,
|
|
1645
1867
|
line: getLineNumber(content, hookStartMatch[0]),
|
|
1646
|
-
reason: `Custom ${type} '${name}' detected that implements role/permission management logic. Even though it may use some pace-core utilities
|
|
1868
|
+
reason: `Custom ${type} '${name}' detected that implements role/permission management logic. Even though it may use some pace-core utilities, it should use pace-core RBAC APIs instead of implementing custom logic.`,
|
|
1647
1869
|
replacement,
|
|
1648
1870
|
severity: 'error'
|
|
1649
1871
|
});
|
|
@@ -1700,7 +1922,7 @@ function scanFile(filePath, manifest) {
|
|
|
1700
1922
|
const operatesOnPagePermissions = /rbac_page_permissions/.test(content);
|
|
1701
1923
|
const operatesOnRoleTables = /rbac_(organisation|event_app|global)_roles/.test(content);
|
|
1702
1924
|
const operatesOnConfigTables = /rbac_apps|rbac_app_pages/.test(content);
|
|
1703
|
-
const usesSecureMethods = /
|
|
1925
|
+
const usesSecureMethods = /useSecureSupabase|secureQuery|secureUpdate|secureInsert|secureDelete/.test(content);
|
|
1704
1926
|
|
|
1705
1927
|
// Only flag as permission management if:
|
|
1706
1928
|
// 1. It's explicitly a permission management component AND operates on permission/role tables
|
|
@@ -1729,8 +1951,8 @@ function scanFile(filePath, manifest) {
|
|
|
1729
1951
|
type: 'configuration management component',
|
|
1730
1952
|
file: relativePath,
|
|
1731
1953
|
line: getLineNumber(content, content.match(componentPattern)[0]),
|
|
1732
|
-
reason: `Configuration management component '${name}' detected. Ensure you're using
|
|
1733
|
-
replacement: 'Use
|
|
1954
|
+
reason: `Configuration management component '${name}' detected. Ensure you're using useSecureSupabase for secure operations on configuration tables.`,
|
|
1955
|
+
replacement: 'Use useSecureSupabase from pace-core for admin operations on configuration tables',
|
|
1734
1956
|
severity: 'warning'
|
|
1735
1957
|
});
|
|
1736
1958
|
}
|
|
@@ -1761,7 +1983,10 @@ function scanFile(filePath, manifest) {
|
|
|
1761
1983
|
}
|
|
1762
1984
|
|
|
1763
1985
|
// Check Vite configuration
|
|
1764
|
-
|
|
1986
|
+
// Skip root-level vite.config files - these are typically for library/monorepo development, not consuming apps
|
|
1987
|
+
// The audit recommendations (exclude @jmruthers/pace-core, dedupe) are for consuming apps, not library dev setups
|
|
1988
|
+
const isRootViteConfig = /^vite\.config\.(ts|js|tsx|jsx)$/.test(relativePath);
|
|
1989
|
+
if (!isRootViteConfig && relativePath.match(/vite\.config\.(ts|js|tsx|jsx)$/)) {
|
|
1765
1990
|
const viteIssues = scanViteConfig(filePath, content, relativePath);
|
|
1766
1991
|
violations.viteConfigIssues.push(...viteIssues);
|
|
1767
1992
|
}
|
|
@@ -1956,633 +2181,454 @@ function scanFile(filePath, manifest) {
|
|
|
1956
2181
|
}
|
|
1957
2182
|
}
|
|
1958
2183
|
|
|
1959
|
-
|
|
1960
|
-
|
|
1961
|
-
|
|
1962
|
-
//
|
|
1963
|
-
|
|
1964
|
-
|
|
1965
|
-
|
|
1966
|
-
// Check if file imports from pace-core
|
|
1967
|
-
const paceCoreImportPattern = /import\s+{([^}]+)}\s+from\s+['"]@jmruthers\/pace-core['"]/;
|
|
1968
|
-
const paceCoreImportMatch = content.match(paceCoreImportPattern);
|
|
1969
|
-
|
|
1970
|
-
// Extract imported pace-core component names
|
|
1971
|
-
let importedPaceCoreComponents = [];
|
|
1972
|
-
if (paceCoreImportMatch) {
|
|
1973
|
-
importedPaceCoreComponents = (paceCoreImportMatch[1] || '')
|
|
1974
|
-
.split(',')
|
|
1975
|
-
.map(name => name.trim().replace(/\s+as\s+\w+/, '')) // Remove aliases
|
|
1976
|
-
.filter(name => manifest.components.includes(name));
|
|
1977
|
-
}
|
|
1978
|
-
|
|
1979
|
-
// Also find all imported components (local and pace-core) to check for wrappers
|
|
1980
|
-
const allImportPattern = /import\s+(?:(?:{([^}]+)})|(\w+))\s+from\s+['"]([^'"]+)['"]/g;
|
|
1981
|
-
const allImports = [];
|
|
1982
|
-
let importMatch;
|
|
1983
|
-
while ((importMatch = allImportPattern.exec(content)) !== null) {
|
|
1984
|
-
const namedImports = importMatch[1]; // { Component1, Component2 }
|
|
1985
|
-
const defaultImport = importMatch[2]; // Component
|
|
1986
|
-
const modulePath = importMatch[3];
|
|
1987
|
-
|
|
1988
|
-
if (namedImports) {
|
|
1989
|
-
namedImports.split(',').forEach(name => {
|
|
1990
|
-
const cleanName = name.trim().replace(/\s+as\s+(\w+)/, ''); // Remove aliases but keep original
|
|
1991
|
-
allImports.push({ name: cleanName, module: modulePath });
|
|
1992
|
-
});
|
|
1993
|
-
}
|
|
1994
|
-
if (defaultImport) {
|
|
1995
|
-
allImports.push({ name: defaultImport, module: modulePath });
|
|
1996
|
-
}
|
|
1997
|
-
}
|
|
1998
|
-
|
|
1999
|
-
// Find exported component definitions
|
|
2000
|
-
// Match: export (default)? (function|const) ComponentName = ...
|
|
2001
|
-
const componentPattern = /export\s+(default\s+)?(function|const)\s+(\w+)\s*[=\(]/g;
|
|
2002
|
-
const componentMatches = [...content.matchAll(componentPattern)];
|
|
2184
|
+
// ============================================
|
|
2185
|
+
// Check for Direct Supabase Client Usage
|
|
2186
|
+
// ============================================
|
|
2187
|
+
// Detect when consuming apps use createClient from @supabase/supabase-js
|
|
2188
|
+
// and then use that client for database queries instead of useSecureSupabase
|
|
2189
|
+
// This is a critical security issue as it bypasses RLS and organisation context
|
|
2003
2190
|
|
|
2004
|
-
|
|
2005
|
-
|
|
2006
|
-
|
|
2191
|
+
// Skip Edge Functions - they run in Deno and must use createClient
|
|
2192
|
+
// Reuse isEdgeFunction declared at the top of the function
|
|
2193
|
+
if (!isEdgeFunction) {
|
|
2194
|
+
// Check for createClient import from @supabase/supabase-js
|
|
2195
|
+
const createClientImportPattern = /import\s+{\s*createClient\s*}\s+from\s+['"]@supabase\/supabase-js['"]/;
|
|
2196
|
+
const hasCreateClientImport = createClientImportPattern.test(content);
|
|
2007
2197
|
|
|
2008
|
-
//
|
|
2009
|
-
|
|
2010
|
-
|
|
2011
|
-
|
|
2012
|
-
}
|
|
2198
|
+
// Check for createClient usage
|
|
2199
|
+
const createClientUsagePattern = /createClient\s*\(/g;
|
|
2200
|
+
const createClientMatches = content.match(createClientUsagePattern);
|
|
2201
|
+
const hasCreateClientUsage = createClientMatches && createClientMatches.length > 0;
|
|
2013
2202
|
|
|
2014
|
-
//
|
|
2015
|
-
|
|
2016
|
-
|
|
2017
|
-
let bodyStart = -1;
|
|
2018
|
-
let bodyEnd = -1;
|
|
2019
|
-
let inBody = false;
|
|
2203
|
+
// Check if file uses useSecureSupabase (correct usage)
|
|
2204
|
+
const usesSecureSupabase = /useSecureSupabase/.test(content) ||
|
|
2205
|
+
/from\s+['"]@jmruthers\/pace-core\/rbac['"]/.test(content);
|
|
2020
2206
|
|
|
2021
|
-
|
|
2022
|
-
|
|
2023
|
-
|
|
2024
|
-
|
|
2025
|
-
|
|
2026
|
-
|
|
2027
|
-
|
|
2028
|
-
braceCount++;
|
|
2029
|
-
} else if (char === '}') {
|
|
2030
|
-
braceCount--;
|
|
2031
|
-
if (braceCount === 0 && inBody) {
|
|
2032
|
-
bodyEnd = i;
|
|
2033
|
-
break;
|
|
2034
|
-
}
|
|
2207
|
+
// Find all variables assigned from createClient
|
|
2208
|
+
const createClientVariablePattern = /(const|let|var)\s+(\w+)\s*=\s*createClient\s*\(/g;
|
|
2209
|
+
const nonSecureClients = new Set();
|
|
2210
|
+
let match;
|
|
2211
|
+
while ((match = createClientVariablePattern.exec(content)) !== null) {
|
|
2212
|
+
if (match[2]) {
|
|
2213
|
+
nonSecureClients.add(match[2]);
|
|
2035
2214
|
}
|
|
2036
2215
|
}
|
|
2037
2216
|
|
|
2038
|
-
|
|
2039
|
-
|
|
2040
|
-
|
|
2041
|
-
|
|
2042
|
-
|
|
2043
|
-
|
|
2044
|
-
|
|
2045
|
-
|
|
2046
|
-
|
|
2047
|
-
|
|
2048
|
-
|
|
2049
|
-
const hasLoops = /for\s*\(|while\s*\(|\.map\s*\(/.test(componentBody);
|
|
2050
|
-
|
|
2051
|
-
// Find all JSX components used in the body
|
|
2052
|
-
// Match JSX opening tags: <ComponentName or <ComponentName>
|
|
2053
|
-
const jsxComponentPattern = /<([A-Z][a-zA-Z0-9]*)[\s>\/]/g;
|
|
2054
|
-
const jsxComponents = [];
|
|
2055
|
-
let jsxMatch;
|
|
2056
|
-
while ((jsxMatch = jsxComponentPattern.exec(componentBody)) !== null) {
|
|
2057
|
-
const jsxComponentName = jsxMatch[1];
|
|
2058
|
-
// Skip React fragments and the component itself
|
|
2059
|
-
if (jsxComponentName !== 'Fragment' &&
|
|
2060
|
-
jsxComponentName !== componentName &&
|
|
2061
|
-
!jsxComponents.includes(jsxComponentName)) {
|
|
2062
|
-
jsxComponents.push(jsxComponentName);
|
|
2217
|
+
// Check for database queries (.from() calls) using non-secure clients
|
|
2218
|
+
// Pattern: variable.from('table_name')
|
|
2219
|
+
const fromPattern = /\.from\s*\(\s*['"]([^'"]+)['"]\s*\)/g;
|
|
2220
|
+
let fromMatch;
|
|
2221
|
+
while ((fromMatch = fromPattern.exec(content)) !== null) {
|
|
2222
|
+
const matchIndex = fromMatch.index;
|
|
2223
|
+
const tableName = fromMatch[1];
|
|
2224
|
+
|
|
2225
|
+
// Skip RBAC tables (already checked above)
|
|
2226
|
+
if (tableName.startsWith('rbac_')) {
|
|
2227
|
+
continue;
|
|
2063
2228
|
}
|
|
2064
|
-
}
|
|
2065
|
-
|
|
2066
|
-
// Check if component is a simple wrapper around another component
|
|
2067
|
-
// Pattern 1: Just returns a single component (pace-core or local)
|
|
2068
|
-
// Pattern 2: Only does auth/permission checks and returns a component
|
|
2069
|
-
// Pattern 3: Minimal logic that just forwards to another component
|
|
2070
|
-
|
|
2071
|
-
// Check for auth/permission check patterns (common in page wrappers)
|
|
2072
|
-
const hasAuthCheck = /useUnifiedAuth|usePermissions|useCan|PagePermissionGuard|PermissionGuard|useRBAC/.test(componentBody);
|
|
2073
|
-
const hasEarlyReturn = /if\s*\([^)]*\)\s*return/.test(componentBody);
|
|
2074
|
-
|
|
2075
|
-
// Count conditional returns (early returns for auth checks)
|
|
2076
|
-
const conditionalReturns = (componentBody.match(/if\s*\([^)]*\)\s*return/g) || []).length;
|
|
2077
|
-
|
|
2078
|
-
// If there's only one JSX component used and minimal logic, it's likely a wrapper
|
|
2079
|
-
if (jsxComponents.length === 1) {
|
|
2080
|
-
const wrappedComponent = jsxComponents[0];
|
|
2081
|
-
const wrappedComponentCount = (componentBody.match(new RegExp(`<${wrappedComponent}`, 'gi')) || []).length;
|
|
2082
|
-
|
|
2083
|
-
// Check if it's a simple wrapper:
|
|
2084
|
-
// 1. Uses only one other component
|
|
2085
|
-
// 2. No state management
|
|
2086
|
-
// 3. No loops
|
|
2087
|
-
// 4. Component appears only once or twice (opening and closing tag)
|
|
2088
|
-
// 5. Either no logic at all, OR only auth/permission checks with early returns
|
|
2089
|
-
|
|
2090
|
-
// Allow auth/permission checks but still flag as wrapper if that's all it does
|
|
2091
|
-
// Pattern: uses auth hook, has early return(s) for auth checks, then returns the wrapped component
|
|
2092
|
-
const hasOnlyAuthLogic = hasAuthCheck &&
|
|
2093
|
-
!hasState &&
|
|
2094
|
-
!hasLoops &&
|
|
2095
|
-
(conditionalReturns === 0 || (conditionalReturns <= 2 && hasEarlyReturn)) &&
|
|
2096
|
-
(!hasConditionals || conditionalReturns <= 2);
|
|
2097
|
-
|
|
2098
|
-
// Check if hooks are only auth-related
|
|
2099
|
-
const authHookPattern = /use(UnifiedAuth|Permissions|Can|RBAC)/;
|
|
2100
|
-
const allHooks = componentBody.match(/use[A-Z]\w+/g) || [];
|
|
2101
|
-
const onlyAuthHooks = allHooks.length === 0 || allHooks.every(hook => authHookPattern.test(hook));
|
|
2102
2229
|
|
|
2103
|
-
|
|
2104
|
-
|
|
2105
|
-
|
|
2106
|
-
|
|
2107
|
-
(hasMultipleReturns === false || (hasMultipleReturns && hasOnlyAuthLogic && conditionalReturns <= 2)) &&
|
|
2108
|
-
(!hasConditionals || hasOnlyAuthLogic) &&
|
|
2109
|
-
(!hasHooks || (onlyAuthHooks && hasOnlyAuthLogic));
|
|
2230
|
+
// Skip if in a line comment
|
|
2231
|
+
const lineStart = content.lastIndexOf('\n', matchIndex) + 1;
|
|
2232
|
+
const lineUpToMatch = content.substring(lineStart, matchIndex);
|
|
2233
|
+
const isInLineComment = /\/\/[^\n]*$/.test(lineUpToMatch);
|
|
2110
2234
|
|
|
2111
|
-
if (
|
|
2112
|
-
|
|
2113
|
-
|
|
2114
|
-
|
|
2115
|
-
|
|
2235
|
+
if (isInLineComment) {
|
|
2236
|
+
continue;
|
|
2237
|
+
}
|
|
2238
|
+
|
|
2239
|
+
// Find the variable name before .from()
|
|
2240
|
+
const beforeMatch = content.substring(Math.max(0, matchIndex - 200), matchIndex);
|
|
2241
|
+
const parts = beforeMatch.split('.from');
|
|
2242
|
+
let variableName = null;
|
|
2243
|
+
if (parts.length > 0) {
|
|
2244
|
+
const beforeFrom = parts[parts.length - 1].trim();
|
|
2245
|
+
const words = beforeFrom.match(/\b\w+\b/g);
|
|
2246
|
+
if (words && words.length > 0) {
|
|
2247
|
+
variableName = words[words.length - 1];
|
|
2248
|
+
}
|
|
2249
|
+
}
|
|
2250
|
+
|
|
2251
|
+
// Check if this variable is from createClient (non-secure)
|
|
2252
|
+
if (variableName && nonSecureClients.has(variableName)) {
|
|
2253
|
+
// Check if it's in a config file (acceptable for centralized config)
|
|
2254
|
+
const isConfigFile = /config|supabase|client/i.test(relativePath) &&
|
|
2255
|
+
(relativePath.includes('supabase.ts') ||
|
|
2256
|
+
relativePath.includes('supabase.js') ||
|
|
2257
|
+
relativePath.includes('client.ts') ||
|
|
2258
|
+
relativePath.includes('client.js'));
|
|
2116
2259
|
|
|
2117
|
-
|
|
2118
|
-
|
|
2119
|
-
|
|
2120
|
-
|
|
2121
|
-
|
|
2122
|
-
|
|
2123
|
-
|
|
2124
|
-
|
|
2125
|
-
|
|
2126
|
-
|
|
2260
|
+
if (!isConfigFile) {
|
|
2261
|
+
const lineNumber = content.substring(0, matchIndex).split('\n').length;
|
|
2262
|
+
|
|
2263
|
+
// Check if this is already reported
|
|
2264
|
+
const alreadyReported = violations.directSupabaseClient.some(v =>
|
|
2265
|
+
v.file === relativePath &&
|
|
2266
|
+
v.variable === variableName &&
|
|
2267
|
+
Math.abs(v.line - lineNumber) <= 2
|
|
2268
|
+
);
|
|
2269
|
+
|
|
2270
|
+
if (!alreadyReported) {
|
|
2271
|
+
violations.directSupabaseClient.push({
|
|
2272
|
+
file: relativePath,
|
|
2273
|
+
line: lineNumber,
|
|
2274
|
+
variable: variableName,
|
|
2275
|
+
table: tableName,
|
|
2276
|
+
reason: `Direct Supabase client usage detected. Variable '${variableName}' is created with createClient() and used for database queries. You MUST use useSecureSupabase() instead to ensure RLS policies and organisation context are enforced.`,
|
|
2277
|
+
recommendation: `Replace with: import { useSecureSupabase } from '@jmruthers/pace-core/rbac'; const ${variableName} = useSecureSupabase();`
|
|
2278
|
+
});
|
|
2279
|
+
}
|
|
2127
2280
|
}
|
|
2281
|
+
}
|
|
2282
|
+
}
|
|
2283
|
+
|
|
2284
|
+
// Also check if file imports createClient but doesn't use useSecureSupabase
|
|
2285
|
+
if (hasCreateClientImport && hasCreateClientUsage && !usesSecureSupabase) {
|
|
2286
|
+
// Check if it's a config file (acceptable)
|
|
2287
|
+
const isConfigFile = /config|supabase|client/i.test(relativePath) &&
|
|
2288
|
+
(relativePath.includes('supabase.ts') ||
|
|
2289
|
+
relativePath.includes('supabase.js') ||
|
|
2290
|
+
relativePath.includes('client.ts') ||
|
|
2291
|
+
relativePath.includes('client.js'));
|
|
2292
|
+
|
|
2293
|
+
if (!isConfigFile) {
|
|
2294
|
+
// Check if createClient is used for queries (not just config)
|
|
2295
|
+
const hasDatabaseQueries = /\.from\s*\(/.test(content);
|
|
2128
2296
|
|
|
2129
|
-
|
|
2130
|
-
|
|
2131
|
-
|
|
2132
|
-
|
|
2133
|
-
|
|
2134
|
-
|
|
2135
|
-
|
|
2136
|
-
|
|
2297
|
+
if (hasDatabaseQueries) {
|
|
2298
|
+
violations.directSupabaseClient.push({
|
|
2299
|
+
file: relativePath,
|
|
2300
|
+
line: 1,
|
|
2301
|
+
variable: 'unknown',
|
|
2302
|
+
table: 'multiple',
|
|
2303
|
+
reason: 'File imports createClient from @supabase/supabase-js and performs database queries. You MUST use useSecureSupabase() instead to ensure RLS policies and organisation context are enforced.',
|
|
2304
|
+
recommendation: 'Replace createClient with: import { useSecureSupabase } from \'@jmruthers/pace-core/rbac\'; const supabase = useSecureSupabase();'
|
|
2305
|
+
});
|
|
2306
|
+
}
|
|
2137
2307
|
}
|
|
2138
2308
|
}
|
|
2139
|
-
});
|
|
2140
|
-
|
|
2141
|
-
return issues;
|
|
2142
|
-
}
|
|
2143
|
-
|
|
2144
|
-
// Get line number for a match
|
|
2145
|
-
function getLineNumber(content, match) {
|
|
2146
|
-
const lines = content.substring(0, content.indexOf(match)).split('\n');
|
|
2147
|
-
return lines.length;
|
|
2148
|
-
}
|
|
2149
|
-
|
|
2150
|
-
// Generate report
|
|
2151
|
-
function generateReport(allViolations, manifest) {
|
|
2152
|
-
const totalRestricted = allViolations.restrictedImports.length;
|
|
2153
|
-
const totalDuplicates =
|
|
2154
|
-
allViolations.duplicateComponents.length +
|
|
2155
|
-
allViolations.duplicateHooks.length +
|
|
2156
|
-
allViolations.duplicateUtils.length;
|
|
2157
|
-
const totalSuggestions = allViolations.suggestions.length;
|
|
2158
|
-
const totalRbacAuth =
|
|
2159
|
-
allViolations.customAuthCode.length +
|
|
2160
|
-
allViolations.duplicateConfig.length +
|
|
2161
|
-
allViolations.unprotectedPages.length +
|
|
2162
|
-
allViolations.directSupabaseAuth.length;
|
|
2163
|
-
const totalSetupIssues =
|
|
2164
|
-
allViolations.providerSetupIssues.length +
|
|
2165
|
-
allViolations.viteConfigIssues.length +
|
|
2166
|
-
allViolations.routerSetupIssues.length;
|
|
2167
|
-
const totalUnnecessaryWrappers = allViolations.unnecessaryWrappers.length;
|
|
2168
|
-
const totalAppDiscovery = allViolations.appDiscoveryIssues.length;
|
|
2169
|
-
const totalIssues = totalRestricted + totalDuplicates + totalSuggestions + totalRbacAuth + totalSetupIssues + totalUnnecessaryWrappers + totalAppDiscovery;
|
|
2170
|
-
|
|
2171
|
-
console.log(`\n${colors.bold}${colors.cyan}═══════════════════════════════════════════════════════════${colors.reset}`);
|
|
2172
|
-
console.log(`${colors.bold}${colors.cyan} pace-core Compliance Report${colors.reset}`);
|
|
2173
|
-
console.log(`${colors.bold}${colors.cyan}═══════════════════════════════════════════════════════════${colors.reset}\n`);
|
|
2174
|
-
|
|
2175
|
-
// Restricted Imports
|
|
2176
|
-
if (totalRestricted > 0) {
|
|
2177
|
-
console.log(`${colors.red}${colors.bold}❌ Restricted Imports Found: ${totalRestricted}${colors.reset}\n`);
|
|
2178
|
-
allViolations.restrictedImports.forEach(({ module, reason, file, line }) => {
|
|
2179
|
-
console.log(` ${colors.red}•${colors.reset} ${colors.yellow}${file}:${line}${colors.reset}`);
|
|
2180
|
-
console.log(` Import: ${colors.cyan}${module}${colors.reset}`);
|
|
2181
|
-
console.log(` ${colors.yellow}Reason:${colors.reset} ${reason}\n`);
|
|
2182
|
-
});
|
|
2183
|
-
} else {
|
|
2184
|
-
console.log(`${colors.green}✅ No restricted imports found${colors.reset}\n`);
|
|
2185
|
-
}
|
|
2186
|
-
|
|
2187
|
-
// Duplicate Components
|
|
2188
|
-
if (allViolations.duplicateComponents.length > 0) {
|
|
2189
|
-
console.log(`${colors.red}${colors.bold}❌ Duplicate Components Found: ${allViolations.duplicateComponents.length}${colors.reset}\n`);
|
|
2190
|
-
allViolations.duplicateComponents.forEach(({ component, file }) => {
|
|
2191
|
-
console.log(` ${colors.red}•${colors.reset} ${colors.yellow}${file}${colors.reset}`);
|
|
2192
|
-
console.log(` Component '${colors.cyan}${component}${colors.reset}' conflicts with pace-core component`);
|
|
2193
|
-
console.log(` ${colors.yellow}Suggestion:${colors.reset} Use '${component}' from '@jmruthers/pace-core' instead\n`);
|
|
2194
|
-
});
|
|
2195
|
-
}
|
|
2196
|
-
|
|
2197
|
-
// Duplicate Hooks
|
|
2198
|
-
if (allViolations.duplicateHooks.length > 0) {
|
|
2199
|
-
console.log(`${colors.red}${colors.bold}❌ Duplicate Hooks Found: ${allViolations.duplicateHooks.length}${colors.reset}\n`);
|
|
2200
|
-
allViolations.duplicateHooks.forEach(({ hook, file }) => {
|
|
2201
|
-
console.log(` ${colors.red}•${colors.reset} ${colors.yellow}${file}${colors.reset}`);
|
|
2202
|
-
console.log(` Hook '${colors.cyan}${hook}${colors.reset}' conflicts with pace-core hook`);
|
|
2203
|
-
console.log(` ${colors.yellow}Suggestion:${colors.reset} Use '${hook}' from '@jmruthers/pace-core' instead\n`);
|
|
2204
|
-
});
|
|
2205
|
-
}
|
|
2206
|
-
|
|
2207
|
-
// Duplicate Utils
|
|
2208
|
-
if (allViolations.duplicateUtils.length > 0) {
|
|
2209
|
-
console.log(`${colors.red}${colors.bold}❌ Duplicate Utils Found: ${allViolations.duplicateUtils.length}${colors.reset}\n`);
|
|
2210
|
-
allViolations.duplicateUtils.forEach(({ util, file }) => {
|
|
2211
|
-
console.log(` ${colors.red}•${colors.reset} ${colors.yellow}${file}${colors.reset}`);
|
|
2212
|
-
console.log(` Util '${colors.cyan}${util}${colors.reset}' conflicts with pace-core util`);
|
|
2213
|
-
console.log(` ${colors.yellow}Suggestion:${colors.reset} Use '${util}' from '@jmruthers/pace-core' instead\n`);
|
|
2214
|
-
});
|
|
2215
2309
|
}
|
|
2216
2310
|
|
|
2217
|
-
//
|
|
2218
|
-
|
|
2219
|
-
|
|
2220
|
-
|
|
2221
|
-
|
|
2222
|
-
|
|
2223
|
-
grouped[s.file].push(s);
|
|
2224
|
-
});
|
|
2225
|
-
Object.entries(grouped).forEach(([file, suggestions]) => {
|
|
2226
|
-
console.log(` ${colors.yellow}•${colors.reset} ${colors.yellow}${file}${colors.reset}`);
|
|
2227
|
-
suggestions.forEach(s => {
|
|
2228
|
-
console.log(` ${s.suggestion}\n`);
|
|
2229
|
-
});
|
|
2230
|
-
});
|
|
2231
|
-
}
|
|
2232
|
-
|
|
2233
|
-
// Unnecessary Wrappers
|
|
2234
|
-
if (totalUnnecessaryWrappers > 0) {
|
|
2235
|
-
console.log(`${colors.yellow}${colors.bold}⚠️ Unnecessary Wrappers: ${totalUnnecessaryWrappers}${colors.reset}\n`);
|
|
2236
|
-
allViolations.unnecessaryWrappers.forEach(({ component, wrappedComponent, file, line, reason, recommendation }) => {
|
|
2237
|
-
console.log(` ${colors.yellow}•${colors.reset} ${colors.yellow}${file}:${line}${colors.reset}`);
|
|
2238
|
-
console.log(` Component: ${colors.cyan}${component}${colors.reset}`);
|
|
2239
|
-
console.log(` Wraps: ${colors.cyan}${wrappedComponent}${colors.reset}`);
|
|
2240
|
-
console.log(` ${colors.yellow}Issue:${colors.reset} ${reason}`);
|
|
2241
|
-
if (recommendation) {
|
|
2242
|
-
console.log(` ${colors.green}Recommendation:${colors.reset} ${recommendation}\n`);
|
|
2243
|
-
} else {
|
|
2244
|
-
console.log(` ${colors.green}Recommendation:${colors.reset} Remove the wrapper and use the component directly.\n`);
|
|
2245
|
-
}
|
|
2246
|
-
});
|
|
2247
|
-
}
|
|
2311
|
+
// ============================================
|
|
2312
|
+
// Check for Deprecated useSecureDataAccess Usage
|
|
2313
|
+
// ============================================
|
|
2314
|
+
// Detect when consuming apps use useSecureDataAccess() with secureQuery/secureInsert/etc
|
|
2315
|
+
// This is deprecated - they should migrate to useSecureSupabase() instead
|
|
2316
|
+
// This helps identify code that needs migration before retiring the old API
|
|
2248
2317
|
|
|
2249
|
-
//
|
|
2250
|
-
if (
|
|
2251
|
-
|
|
2252
|
-
|
|
2253
|
-
|
|
2254
|
-
|
|
2255
|
-
// Separate by type
|
|
2256
|
-
const directQueries = allViolations.appDiscoveryIssues.filter(v => v.type === 'direct_table_query');
|
|
2257
|
-
const hardcodedNames = allViolations.appDiscoveryIssues.filter(v => v.type === 'hardcoded_app_name');
|
|
2258
|
-
const suggestions = allViolations.appDiscoveryIssues.filter(v => v.type === 'suggestion');
|
|
2318
|
+
// Skip Edge Functions - they run in Deno
|
|
2319
|
+
if (!isEdgeFunction) {
|
|
2320
|
+
// Check for useSecureDataAccess import
|
|
2321
|
+
const hasSecureDataAccessImport = /import.*useSecureDataAccess.*from\s+['"]@jmruthers\/pace-core['"]/.test(content) ||
|
|
2322
|
+
/import.*useSecureDataAccess.*from\s+['"]@jmruthers\/pace-core\/hooks['"]/.test(content);
|
|
2259
2323
|
|
|
2260
|
-
|
|
2261
|
-
|
|
2262
|
-
directQueries.forEach(({ file, line, reason, recommendation }) => {
|
|
2263
|
-
console.log(` ${colors.yellow}•${colors.reset} ${colors.yellow}${file}:${line}${colors.reset}`);
|
|
2264
|
-
console.log(` ${colors.yellow}Issue:${colors.reset} ${reason}`);
|
|
2265
|
-
console.log(` ${colors.green}Fix:${colors.reset} ${recommendation}\n`);
|
|
2266
|
-
});
|
|
2267
|
-
}
|
|
2324
|
+
// Check for useSecureDataAccess hook usage
|
|
2325
|
+
const hasSecureDataAccessHook = /useSecureDataAccess\s*\(/.test(content);
|
|
2268
2326
|
|
|
2269
|
-
|
|
2270
|
-
|
|
2271
|
-
|
|
2272
|
-
|
|
2273
|
-
|
|
2274
|
-
|
|
2275
|
-
|
|
2276
|
-
|
|
2277
|
-
}
|
|
2327
|
+
// Check for deprecated secure methods
|
|
2328
|
+
const deprecatedMethods = [
|
|
2329
|
+
{ name: 'secureQuery', operation: 'query' },
|
|
2330
|
+
{ name: 'secureInsert', operation: 'insert' },
|
|
2331
|
+
{ name: 'secureUpdate', operation: 'update' },
|
|
2332
|
+
{ name: 'secureDelete', operation: 'delete' },
|
|
2333
|
+
{ name: 'secureRpc', operation: 'RPC call' }
|
|
2334
|
+
];
|
|
2278
2335
|
|
|
2279
|
-
|
|
2280
|
-
|
|
2281
|
-
suggestions.forEach(({ file, reason, recommendation }) => {
|
|
2282
|
-
console.log(` ${colors.cyan}•${colors.reset} ${colors.yellow}${file}${colors.reset}`);
|
|
2283
|
-
console.log(` ${reason}`);
|
|
2284
|
-
console.log(` ${colors.green}Recommendation:${colors.reset} ${recommendation}\n`);
|
|
2285
|
-
});
|
|
2286
|
-
}
|
|
2336
|
+
// Pattern to find destructured secure methods from useSecureDataAccess
|
|
2337
|
+
const destructurePattern = /(const|let)\s*\{[^}]*\b(secureQuery|secureInsert|secureUpdate|secureDelete|secureRpc)\b[^}]*\}\s*=\s*useSecureDataAccess\s*\(/g;
|
|
2287
2338
|
|
|
2288
|
-
|
|
2289
|
-
|
|
2290
|
-
|
|
2291
|
-
|
|
2292
|
-
|
|
2293
|
-
|
|
2294
|
-
|
|
2295
|
-
// RBAC/Auth Compliance Section
|
|
2296
|
-
if (totalRbacAuth > 0) {
|
|
2297
|
-
console.log(`\n${colors.bold}${colors.cyan}═══════════════════════════════════════════════════════════${colors.reset}`);
|
|
2298
|
-
console.log(`${colors.bold}${colors.cyan} RBAC/Auth Compliance${colors.reset}`);
|
|
2299
|
-
console.log(`${colors.bold}${colors.cyan}═══════════════════════════════════════════════════════════${colors.reset}\n`);
|
|
2339
|
+
// Pattern to find direct method calls (secureQuery(...), secureInsert(...), etc.)
|
|
2340
|
+
const methodCallPatterns = deprecatedMethods.map(method => ({
|
|
2341
|
+
name: method.name,
|
|
2342
|
+
operation: method.operation,
|
|
2343
|
+
pattern: new RegExp(`\\b${method.name}\\s*\\(`, 'g')
|
|
2344
|
+
}));
|
|
2300
2345
|
|
|
2301
|
-
//
|
|
2302
|
-
if (
|
|
2303
|
-
//
|
|
2304
|
-
|
|
2305
|
-
const
|
|
2306
|
-
|
|
2307
|
-
|
|
2308
|
-
|
|
2309
|
-
|
|
2310
|
-
|
|
2311
|
-
|
|
2312
|
-
console.log(` ${type}: ${colors.cyan}${name}${colors.reset}`);
|
|
2313
|
-
console.log(` ${colors.yellow}Reason:${colors.reset} ${reason}`);
|
|
2314
|
-
if (replacement) {
|
|
2315
|
-
console.log(` ${colors.green}Fix:${colors.reset} ${replacement}`);
|
|
2316
|
-
// Add example code if provided
|
|
2317
|
-
if (example) {
|
|
2318
|
-
console.log(` ${colors.cyan}Example:${colors.reset}`);
|
|
2319
|
-
example.split('\n').forEach(line => {
|
|
2320
|
-
console.log(` ${colors.green}${line}${colors.reset}`);
|
|
2321
|
-
});
|
|
2322
|
-
} else {
|
|
2323
|
-
// Add example code for common cases
|
|
2324
|
-
if (type === 'rbac query' && name.includes('rbac_user_profiles')) {
|
|
2325
|
-
console.log(` ${colors.cyan}Example:${colors.reset}`);
|
|
2326
|
-
console.log(` ${colors.green}import { useSecureSupabase } from '@jmruthers/pace-core/rbac';${colors.reset}`);
|
|
2327
|
-
console.log(` ${colors.green}const supabase = useSecureSupabase();${colors.reset}`);
|
|
2328
|
-
console.log(` ${colors.green}const { data } = await supabase.from('rbac_user_profiles').select('*');${colors.reset}`);
|
|
2329
|
-
} else if (type === 'rbac query' && name.includes('rbac_user_login_history')) {
|
|
2330
|
-
console.log(` ${colors.cyan}Example:${colors.reset}`);
|
|
2331
|
-
console.log(` ${colors.green}import { useSecureSupabase } from '@jmruthers/pace-core/rbac';${colors.reset}`);
|
|
2332
|
-
console.log(` ${colors.green}const supabase = useSecureSupabase();${colors.reset}`);
|
|
2333
|
-
console.log(` ${colors.green}const { data } = await supabase.from('rbac_user_login_history').select('*').eq('user_id', userId);${colors.reset}`);
|
|
2334
|
-
console.log(` ${colors.yellow}Note:${colors.reset} Login history is automatically tracked by UnifiedAuthProvider.`);
|
|
2335
|
-
console.log(` ${colors.yellow} ${colors.reset} Queries should use useSecureSupabase to ensure organisation context is enforced.`);
|
|
2336
|
-
} else if (type === 'rbac query' && name.includes('rbac_user_units')) {
|
|
2337
|
-
console.log(` ${colors.cyan}Example:${colors.reset}`);
|
|
2338
|
-
console.log(` ${colors.green}import { useSecureSupabase } from '@jmruthers/pace-core/rbac';${colors.reset}`);
|
|
2339
|
-
console.log(` ${colors.green}const supabase = useSecureSupabase();${colors.reset}`);
|
|
2340
|
-
console.log(` ${colors.green}// For reading, use RPC:${colors.reset}`);
|
|
2341
|
-
console.log(` ${colors.green}const { data } = await supabase.rpc('data_user_unit_get', {${colors.reset}`);
|
|
2342
|
-
console.log(` ${colors.green} p_user_id: userId,${colors.reset}`);
|
|
2343
|
-
console.log(` ${colors.green} p_event_id: eventId${colors.reset}`);
|
|
2344
|
-
console.log(` ${colors.green}});${colors.reset}`);
|
|
2345
|
-
} else if (type === 'rbac query' && name.includes('rbac_apps')) {
|
|
2346
|
-
console.log(` ${colors.cyan}Example (app discovery):${colors.reset}`);
|
|
2347
|
-
console.log(` ${colors.green}const { data: apps } = await supabase.rpc('data_rbac_apps_list');${colors.reset}`);
|
|
2348
|
-
console.log(` ${colors.yellow}Note:${colors.reset} Use data_rbac_apps_list RPC function for dynamic app discovery instead of querying rbac_apps directly.`);
|
|
2349
|
-
} else if (type === 'rbac query' && (name.includes('rbac_app_pages') || name.includes('rbac_page_permissions'))) {
|
|
2350
|
-
console.log(` ${colors.cyan}Example (admin operations):${colors.reset}`);
|
|
2351
|
-
console.log(` ${colors.green}import { useSecureSupabase } from '@jmruthers/pace-core/rbac';${colors.reset}`);
|
|
2352
|
-
console.log(` ${colors.green}const supabase = useSecureSupabase();${colors.reset}`);
|
|
2353
|
-
console.log(` ${colors.green}const { data } = await supabase.from('${name.match(/rbac_\w+/)?.[0] || 'rbac_table'}').select('*');${colors.reset}`);
|
|
2354
|
-
}
|
|
2355
|
-
}
|
|
2346
|
+
// Check if file uses the deprecated hook
|
|
2347
|
+
if (hasSecureDataAccessImport || hasSecureDataAccessHook) {
|
|
2348
|
+
// Find all destructured methods
|
|
2349
|
+
let destructureMatch;
|
|
2350
|
+
const foundMethods = new Set();
|
|
2351
|
+
|
|
2352
|
+
while ((destructureMatch = destructurePattern.exec(content)) !== null) {
|
|
2353
|
+
const destructureText = destructureMatch[0];
|
|
2354
|
+
deprecatedMethods.forEach(method => {
|
|
2355
|
+
if (destructureText.includes(method.name)) {
|
|
2356
|
+
foundMethods.add(method.name);
|
|
2356
2357
|
}
|
|
2357
|
-
console.log('');
|
|
2358
2358
|
});
|
|
2359
2359
|
}
|
|
2360
2360
|
|
|
2361
|
-
|
|
2362
|
-
|
|
2363
|
-
|
|
2364
|
-
|
|
2365
|
-
|
|
2366
|
-
|
|
2367
|
-
|
|
2368
|
-
|
|
2361
|
+
// Find all method calls
|
|
2362
|
+
methodCallPatterns.forEach(({ name, operation, pattern }) => {
|
|
2363
|
+
let match;
|
|
2364
|
+
pattern.lastIndex = 0;
|
|
2365
|
+
|
|
2366
|
+
while ((match = pattern.exec(content)) !== null) {
|
|
2367
|
+
const matchIndex = match.index;
|
|
2368
|
+
|
|
2369
|
+
// Skip if in a line comment
|
|
2370
|
+
const lineStart = content.lastIndexOf('\n', matchIndex) + 1;
|
|
2371
|
+
const lineUpToMatch = content.substring(lineStart, matchIndex);
|
|
2372
|
+
const isInLineComment = /\/\/[^\n]*$/.test(lineUpToMatch);
|
|
2373
|
+
|
|
2374
|
+
if (isInLineComment) {
|
|
2375
|
+
continue;
|
|
2369
2376
|
}
|
|
2370
|
-
|
|
2371
|
-
|
|
2372
|
-
|
|
2373
|
-
|
|
2374
|
-
|
|
2377
|
+
|
|
2378
|
+
// Check if this is from useSecureDataAccess (not from a different source)
|
|
2379
|
+
// Look backwards to see if it's from useSecureDataAccess destructuring
|
|
2380
|
+
const beforeMatch = content.substring(Math.max(0, matchIndex - 500), matchIndex);
|
|
2381
|
+
const isFromSecureDataAccess =
|
|
2382
|
+
/useSecureDataAccess\s*\(/.test(beforeMatch) ||
|
|
2383
|
+
/(const|let)\s*\{[^}]*\bsecure(Query|Insert|Update|Delete|Rpc)\b/.test(beforeMatch);
|
|
2384
|
+
|
|
2385
|
+
if (isFromSecureDataAccess) {
|
|
2386
|
+
foundMethods.add(name);
|
|
2387
|
+
|
|
2388
|
+
const lineNumber = content.substring(0, matchIndex).split('\n').length;
|
|
2389
|
+
|
|
2390
|
+
// Check if already reported
|
|
2391
|
+
const alreadyReported = violations.deprecatedSecureDataAccess.some(v =>
|
|
2392
|
+
v.file === relativePath &&
|
|
2393
|
+
v.method === name &&
|
|
2394
|
+
Math.abs(v.line - lineNumber) <= 2
|
|
2395
|
+
);
|
|
2396
|
+
|
|
2397
|
+
if (!alreadyReported) {
|
|
2398
|
+
violations.deprecatedSecureDataAccess.push({
|
|
2399
|
+
file: relativePath,
|
|
2400
|
+
line: lineNumber,
|
|
2401
|
+
method: name,
|
|
2402
|
+
operation: operation,
|
|
2403
|
+
reason: `Deprecated method '${name}' from useSecureDataAccess() detected. This API is being retired. Migrate to useSecureSupabase() instead.`,
|
|
2404
|
+
recommendation: getMigrationRecommendation(name, operation)
|
|
2405
|
+
});
|
|
2406
|
+
}
|
|
2375
2407
|
}
|
|
2376
|
-
console.log('');
|
|
2377
|
-
});
|
|
2378
|
-
}
|
|
2379
|
-
|
|
2380
|
-
// Don't show info-level items (these are correct usage)
|
|
2381
|
-
// They're tracked but not displayed to avoid noise
|
|
2382
|
-
}
|
|
2383
|
-
|
|
2384
|
-
// Duplicate Configurations
|
|
2385
|
-
if (allViolations.duplicateConfig.length > 0) {
|
|
2386
|
-
console.log(`${colors.red}${colors.bold}❌ Duplicate Configurations Found: ${allViolations.duplicateConfig.length}${colors.reset}\n`);
|
|
2387
|
-
allViolations.duplicateConfig.forEach(({ type, file, count, reason }) => {
|
|
2388
|
-
console.log(` ${colors.red}•${colors.reset} ${colors.yellow}${file}${colors.reset}`);
|
|
2389
|
-
console.log(` Type: ${colors.cyan}${type}${colors.reset}${count ? ` (${count} instances)` : ''}`);
|
|
2390
|
-
console.log(` ${colors.yellow}Reason:${colors.reset} ${reason}\n`);
|
|
2391
|
-
});
|
|
2392
|
-
}
|
|
2393
|
-
|
|
2394
|
-
// Unprotected Pages
|
|
2395
|
-
if (allViolations.unprotectedPages.length > 0) {
|
|
2396
|
-
console.log(`${colors.red}${colors.bold}❌ Unprotected Pages Found: ${allViolations.unprotectedPages.length}${colors.reset}\n`);
|
|
2397
|
-
allViolations.unprotectedPages.forEach(({ file, reason }) => {
|
|
2398
|
-
console.log(` ${colors.red}•${colors.reset} ${colors.yellow}${file}${colors.reset}`);
|
|
2399
|
-
console.log(` ${colors.yellow}Reason:${colors.reset} ${reason}\n`);
|
|
2400
|
-
});
|
|
2401
|
-
}
|
|
2402
|
-
|
|
2403
|
-
// Direct Supabase Auth Usage
|
|
2404
|
-
if (allViolations.directSupabaseAuth.length > 0) {
|
|
2405
|
-
console.log(`${colors.red}${colors.bold}❌ Direct Supabase Auth Usage Found: ${allViolations.directSupabaseAuth.length}${colors.reset}\n`);
|
|
2406
|
-
allViolations.directSupabaseAuth.forEach(({ file, line, reason, method, recommendation }) => {
|
|
2407
|
-
console.log(` ${colors.red}•${colors.reset} ${colors.yellow}${file}:${line}${colors.reset}`);
|
|
2408
|
-
console.log(` ${colors.yellow}Reason:${colors.reset} ${reason}`);
|
|
2409
|
-
if (recommendation) {
|
|
2410
|
-
console.log(` ${colors.green}Fix:${colors.reset} ${recommendation}\n`);
|
|
2411
|
-
} else {
|
|
2412
|
-
console.log(` ${colors.green}Fix:${colors.reset} Use ${colors.cyan}useUnifiedAuth${colors.reset} hook from @jmruthers/pace-core instead of direct ${method ? `supabase.auth.${method}()` : 'Supabase auth'} calls\n`);
|
|
2413
2408
|
}
|
|
2414
2409
|
});
|
|
2415
|
-
|
|
2416
|
-
|
|
2417
|
-
|
|
2418
|
-
|
|
2419
|
-
|
|
2420
|
-
|
|
2421
|
-
|
|
2422
|
-
|
|
2423
|
-
|
|
2424
|
-
|
|
2425
|
-
|
|
2426
|
-
|
|
2427
|
-
|
|
2428
|
-
|
|
2429
|
-
|
|
2430
|
-
console.log(` ${colors.red}•${colors.reset} ${colors.yellow}${file}:${line}${colors.reset}`);
|
|
2431
|
-
if (provider) {
|
|
2432
|
-
console.log(` Missing Provider: ${colors.cyan}${provider}${colors.reset}`);
|
|
2433
|
-
}
|
|
2434
|
-
if (type) {
|
|
2435
|
-
console.log(` Issue Type: ${colors.cyan}${type}${colors.reset}`);
|
|
2410
|
+
|
|
2411
|
+
// If we found the hook usage but haven't reported specific methods, add a general warning
|
|
2412
|
+
if (foundMethods.size === 0 && hasSecureDataAccessHook) {
|
|
2413
|
+
// Check if it's just imported but not used, or used in a way we didn't detect
|
|
2414
|
+
const hasAnySecureMethodCall = /secure(Query|Insert|Update|Delete|Rpc)\s*\(/.test(content);
|
|
2415
|
+
|
|
2416
|
+
if (hasAnySecureMethodCall) {
|
|
2417
|
+
violations.deprecatedSecureDataAccess.push({
|
|
2418
|
+
file: relativePath,
|
|
2419
|
+
line: 1,
|
|
2420
|
+
method: 'useSecureDataAccess',
|
|
2421
|
+
operation: 'general',
|
|
2422
|
+
reason: 'useSecureDataAccess() hook detected. This API is deprecated and will be retired. Migrate to useSecureSupabase() instead.',
|
|
2423
|
+
recommendation: 'Replace useSecureDataAccess() with useSecureSupabase() and use standard Supabase query builder API (.from(), .select(), etc.)'
|
|
2424
|
+
});
|
|
2436
2425
|
}
|
|
2437
|
-
|
|
2438
|
-
console.log(` ${colors.green}Fix:${colors.reset} ${recommendation}\n`);
|
|
2439
|
-
});
|
|
2440
|
-
}
|
|
2441
|
-
|
|
2442
|
-
// Vite Configuration Issues
|
|
2443
|
-
if (allViolations.viteConfigIssues.length > 0) {
|
|
2444
|
-
console.log(`${colors.red}${colors.bold}❌ Vite Configuration Issues Found: ${allViolations.viteConfigIssues.length}${colors.reset}\n`);
|
|
2445
|
-
allViolations.viteConfigIssues.forEach(({ file, line, type, reason, recommendation }) => {
|
|
2446
|
-
const severity = type === 'recommendation' ? colors.yellow : colors.red;
|
|
2447
|
-
const icon = type === 'recommendation' ? '💡' : '❌';
|
|
2448
|
-
console.log(` ${severity}${icon}${colors.reset} ${colors.yellow}${file}:${line}${colors.reset}`);
|
|
2449
|
-
console.log(` ${colors.yellow}Issue:${colors.reset} ${reason}`);
|
|
2450
|
-
console.log(` ${colors.green}Fix:${colors.reset} ${recommendation}\n`);
|
|
2451
|
-
});
|
|
2452
|
-
}
|
|
2453
|
-
|
|
2454
|
-
// Router Setup Issues
|
|
2455
|
-
if (allViolations.routerSetupIssues.length > 0) {
|
|
2456
|
-
console.log(`${colors.red}${colors.bold}❌ Router Setup Issues Found: ${allViolations.routerSetupIssues.length}${colors.reset}\n`);
|
|
2457
|
-
allViolations.routerSetupIssues.forEach(({ file, line, type, reason, recommendation }) => {
|
|
2458
|
-
console.log(` ${colors.red}•${colors.reset} ${colors.yellow}${file}:${line}${colors.reset}`);
|
|
2459
|
-
console.log(` Issue Type: ${colors.cyan}${type}${colors.reset}`);
|
|
2460
|
-
console.log(` ${colors.yellow}Problem:${colors.reset} ${reason}`);
|
|
2461
|
-
console.log(` ${colors.green}Fix:${colors.reset} ${recommendation}\n`);
|
|
2462
|
-
});
|
|
2426
|
+
}
|
|
2463
2427
|
}
|
|
2464
|
-
} else {
|
|
2465
|
-
console.log(`\n${colors.green}✅ Setup & Configuration: All checks passed${colors.reset}\n`);
|
|
2466
2428
|
}
|
|
2467
2429
|
|
|
2468
|
-
|
|
2469
|
-
console.log(`${colors.bold}${colors.cyan}═══════════════════════════════════════════════════════════${colors.reset}`);
|
|
2470
|
-
console.log(`${colors.bold}Summary:${colors.reset}`);
|
|
2471
|
-
console.log(` Total Issues: ${totalIssues > 0 ? colors.red : colors.green}${totalIssues}${colors.reset}`);
|
|
2472
|
-
console.log(` - Restricted Imports: ${totalRestricted > 0 ? colors.red : colors.green}${totalRestricted}${colors.reset}`);
|
|
2473
|
-
console.log(` - Duplicate Components/Hooks/Utils: ${totalDuplicates > 0 ? colors.red : colors.green}${totalDuplicates}${colors.reset}`);
|
|
2474
|
-
console.log(` - Suggestions: ${colors.yellow}${totalSuggestions}${colors.reset}`);
|
|
2475
|
-
console.log(` - RBAC/Auth Issues: ${totalRbacAuth > 0 ? colors.red : colors.green}${totalRbacAuth}${colors.reset}`);
|
|
2476
|
-
console.log(` - Setup/Configuration Issues: ${totalSetupIssues > 0 ? colors.red : colors.green}${totalSetupIssues}${colors.reset}`);
|
|
2477
|
-
console.log(` - Unnecessary Wrappers: ${totalUnnecessaryWrappers > 0 ? colors.yellow : colors.green}${totalUnnecessaryWrappers}${colors.reset}`);
|
|
2478
|
-
console.log(` - App Discovery Issues: ${totalAppDiscovery > 0 ? colors.yellow : colors.green}${totalAppDiscovery}${colors.reset}`);
|
|
2479
|
-
|
|
2480
|
-
if (totalIssues === 0) {
|
|
2481
|
-
console.log(`\n${colors.green}${colors.bold}✅ Excellent! Your codebase is fully compliant with pace-core standards.${colors.reset}\n`);
|
|
2482
|
-
return 0;
|
|
2483
|
-
} else {
|
|
2484
|
-
console.log(`\n${colors.yellow}${colors.bold}⚠️ Please review the issues above and migrate to pace-core components/hooks/utils.${colors.reset}\n`);
|
|
2485
|
-
return 1;
|
|
2486
|
-
}
|
|
2430
|
+
return violations;
|
|
2487
2431
|
}
|
|
2488
2432
|
|
|
2489
|
-
|
|
2490
|
-
|
|
2491
|
-
|
|
2492
|
-
|
|
2493
|
-
|
|
2494
|
-
|
|
2495
|
-
|
|
2496
|
-
|
|
2433
|
+
/**
|
|
2434
|
+
* Compliance check module
|
|
2435
|
+
*/
|
|
2436
|
+
const complianceCheck = {
|
|
2437
|
+
name: 'compliance',
|
|
2438
|
+
description: 'pace-core compliance checks (restricted imports, duplicates, auth/RBAC, etc.)',
|
|
2439
|
+
severity: 'error',
|
|
2440
|
+
|
|
2441
|
+
async run(context) {
|
|
2442
|
+
const { projectRoot, files, manifest: providedManifest } = context;
|
|
2497
2443
|
|
|
2498
|
-
|
|
2499
|
-
|
|
2500
|
-
|
|
2501
|
-
|
|
2502
|
-
|
|
2503
|
-
|
|
2504
|
-
|
|
2505
|
-
|
|
2506
|
-
|
|
2507
|
-
|
|
2508
|
-
|
|
2444
|
+
// Load manifest if not provided
|
|
2445
|
+
const manifest = providedManifest || loadManifest();
|
|
2446
|
+
|
|
2447
|
+
if (!files || files.length === 0) {
|
|
2448
|
+
return {
|
|
2449
|
+
issues: [],
|
|
2450
|
+
warnings: [],
|
|
2451
|
+
suggestions: [],
|
|
2452
|
+
violations: {
|
|
2453
|
+
restrictedImports: [],
|
|
2454
|
+
duplicateComponents: [],
|
|
2455
|
+
duplicateHooks: [],
|
|
2456
|
+
duplicateUtils: [],
|
|
2457
|
+
suggestions: [],
|
|
2458
|
+
customAuthCode: [],
|
|
2459
|
+
duplicateConfig: [],
|
|
2460
|
+
unprotectedPages: [],
|
|
2461
|
+
directSupabaseAuth: [],
|
|
2462
|
+
directSupabaseClient: [],
|
|
2463
|
+
deprecatedSecureDataAccess: [],
|
|
2464
|
+
providerSetupIssues: [],
|
|
2465
|
+
viteConfigIssues: [],
|
|
2466
|
+
routerSetupIssues: [],
|
|
2467
|
+
unnecessaryWrappers: [],
|
|
2468
|
+
appDiscoveryIssues: []
|
|
2509
2469
|
}
|
|
2470
|
+
};
|
|
2471
|
+
}
|
|
2472
|
+
|
|
2473
|
+
// Aggregate all violations
|
|
2474
|
+
const allViolations = {
|
|
2475
|
+
restrictedImports: [],
|
|
2476
|
+
duplicateComponents: [],
|
|
2477
|
+
duplicateHooks: [],
|
|
2478
|
+
duplicateUtils: [],
|
|
2479
|
+
suggestions: [],
|
|
2480
|
+
customAuthCode: [],
|
|
2481
|
+
duplicateConfig: [],
|
|
2482
|
+
unprotectedPages: [],
|
|
2483
|
+
directSupabaseAuth: [],
|
|
2484
|
+
directSupabaseClient: [],
|
|
2485
|
+
deprecatedSecureDataAccess: [],
|
|
2486
|
+
providerSetupIssues: [],
|
|
2487
|
+
viteConfigIssues: [],
|
|
2488
|
+
routerSetupIssues: [],
|
|
2489
|
+
unnecessaryWrappers: [],
|
|
2490
|
+
appDiscoveryIssues: []
|
|
2491
|
+
};
|
|
2492
|
+
|
|
2493
|
+
// Scan all files
|
|
2494
|
+
for (const filePath of files) {
|
|
2495
|
+
try {
|
|
2496
|
+
const violations = scanFile(filePath, manifest, projectRoot);
|
|
2497
|
+
|
|
2498
|
+
// Aggregate violations
|
|
2499
|
+
Object.keys(allViolations).forEach(key => {
|
|
2500
|
+
if (violations[key] && Array.isArray(violations[key])) {
|
|
2501
|
+
allViolations[key].push(...violations[key]);
|
|
2502
|
+
}
|
|
2503
|
+
});
|
|
2504
|
+
} catch (error) {
|
|
2505
|
+
// Skip files with errors
|
|
2506
|
+
console.warn(`Error scanning ${filePath}: ${error.message}`);
|
|
2510
2507
|
}
|
|
2511
|
-
});
|
|
2512
|
-
} catch (error) {
|
|
2513
|
-
// Skip directories we can't read
|
|
2514
|
-
}
|
|
2515
|
-
|
|
2516
|
-
return fileList;
|
|
2517
|
-
}
|
|
2518
|
-
|
|
2519
|
-
// Main function
|
|
2520
|
-
function main() {
|
|
2521
|
-
const manifest = loadManifest();
|
|
2522
|
-
const projectRoot = findProjectRoot();
|
|
2523
|
-
|
|
2524
|
-
console.log(`${colors.cyan}Scanning project at: ${projectRoot}${colors.reset}`);
|
|
2525
|
-
|
|
2526
|
-
// Find all TypeScript/JavaScript files (excluding node_modules, dist, etc.)
|
|
2527
|
-
const files = findSourceFiles(projectRoot);
|
|
2528
|
-
|
|
2529
|
-
console.log(`Found ${files.length} files to scan...\n`);
|
|
2530
|
-
|
|
2531
|
-
// Scan all files
|
|
2532
|
-
const allViolations = {
|
|
2533
|
-
restrictedImports: [],
|
|
2534
|
-
duplicateComponents: [],
|
|
2535
|
-
duplicateHooks: [],
|
|
2536
|
-
duplicateUtils: [],
|
|
2537
|
-
suggestions: [],
|
|
2538
|
-
customAuthCode: [],
|
|
2539
|
-
duplicateConfig: [],
|
|
2540
|
-
unprotectedPages: [],
|
|
2541
|
-
directSupabaseAuth: [],
|
|
2542
|
-
providerSetupIssues: [],
|
|
2543
|
-
viteConfigIssues: [],
|
|
2544
|
-
routerSetupIssues: [],
|
|
2545
|
-
unnecessaryWrappers: [],
|
|
2546
|
-
appDiscoveryIssues: []
|
|
2547
|
-
};
|
|
2548
|
-
|
|
2549
|
-
files.forEach(file => {
|
|
2550
|
-
try {
|
|
2551
|
-
const violations = scanFile(file, manifest);
|
|
2552
|
-
allViolations.restrictedImports.push(...violations.restrictedImports);
|
|
2553
|
-
allViolations.duplicateComponents.push(...violations.duplicateComponents);
|
|
2554
|
-
allViolations.duplicateHooks.push(...violations.duplicateHooks);
|
|
2555
|
-
allViolations.duplicateUtils.push(...violations.duplicateUtils);
|
|
2556
|
-
allViolations.suggestions.push(...violations.suggestions);
|
|
2557
|
-
allViolations.customAuthCode.push(...violations.customAuthCode);
|
|
2558
|
-
allViolations.duplicateConfig.push(...violations.duplicateConfig);
|
|
2559
|
-
allViolations.unprotectedPages.push(...violations.unprotectedPages);
|
|
2560
|
-
allViolations.directSupabaseAuth.push(...violations.directSupabaseAuth);
|
|
2561
|
-
allViolations.providerSetupIssues.push(...violations.providerSetupIssues);
|
|
2562
|
-
allViolations.viteConfigIssues.push(...violations.viteConfigIssues);
|
|
2563
|
-
allViolations.routerSetupIssues.push(...violations.routerSetupIssues);
|
|
2564
|
-
allViolations.unnecessaryWrappers.push(...violations.unnecessaryWrappers);
|
|
2565
|
-
allViolations.appDiscoveryIssues.push(...violations.appDiscoveryIssues);
|
|
2566
|
-
} catch (error) {
|
|
2567
|
-
console.error(`${colors.red}Error scanning ${file}: ${error.message}${colors.reset}`);
|
|
2568
2508
|
}
|
|
2569
|
-
|
|
2570
|
-
|
|
2571
|
-
|
|
2572
|
-
|
|
2573
|
-
|
|
2574
|
-
|
|
2575
|
-
|
|
2576
|
-
|
|
2577
|
-
|
|
2578
|
-
|
|
2579
|
-
|
|
2580
|
-
|
|
2581
|
-
|
|
2582
|
-
|
|
2583
|
-
|
|
2509
|
+
|
|
2510
|
+
// Convert violations to issues/warnings/suggestions format
|
|
2511
|
+
const issues = [
|
|
2512
|
+
...allViolations.restrictedImports.map(v => ({
|
|
2513
|
+
type: 'restricted-import',
|
|
2514
|
+
file: v.file,
|
|
2515
|
+
line: v.line,
|
|
2516
|
+
message: `Restricted import: ${v.module} - ${v.reason}`,
|
|
2517
|
+
recommendation: `Use pace-core alternative instead`
|
|
2518
|
+
})),
|
|
2519
|
+
...allViolations.duplicateComponents.map(v => ({
|
|
2520
|
+
type: 'duplicate-component',
|
|
2521
|
+
file: v.file,
|
|
2522
|
+
message: `Duplicate component: ${v.component}`,
|
|
2523
|
+
recommendation: `Use ${v.component} from '@jmruthers/pace-core' instead`
|
|
2524
|
+
})),
|
|
2525
|
+
...allViolations.duplicateHooks.map(v => ({
|
|
2526
|
+
type: 'duplicate-hook',
|
|
2527
|
+
file: v.file,
|
|
2528
|
+
message: `Duplicate hook: ${v.hook}`,
|
|
2529
|
+
recommendation: `Use ${v.hook} from '@jmruthers/pace-core' instead`
|
|
2530
|
+
})),
|
|
2531
|
+
...allViolations.duplicateUtils.map(v => ({
|
|
2532
|
+
type: 'duplicate-util',
|
|
2533
|
+
file: v.file,
|
|
2534
|
+
message: `Duplicate util: ${v.util}`,
|
|
2535
|
+
recommendation: `Use ${v.util} from '@jmruthers/pace-core' instead`
|
|
2536
|
+
})),
|
|
2537
|
+
...allViolations.customAuthCode.filter(v => !v.severity || v.severity === 'error').map(v => ({
|
|
2538
|
+
type: 'custom-auth-code',
|
|
2539
|
+
file: v.file,
|
|
2540
|
+
line: v.line,
|
|
2541
|
+
message: `${v.type}: ${v.name} - ${v.reason}`,
|
|
2542
|
+
recommendation: v.replacement || 'Use pace-core APIs instead'
|
|
2543
|
+
})),
|
|
2544
|
+
...allViolations.directSupabaseClient.map(v => ({
|
|
2545
|
+
type: 'direct-supabase-client',
|
|
2546
|
+
file: v.file,
|
|
2547
|
+
line: v.line,
|
|
2548
|
+
message: `Direct Supabase client usage: ${v.reason}`,
|
|
2549
|
+
recommendation: v.recommendation || 'Use useSecureSupabase() instead'
|
|
2550
|
+
})),
|
|
2551
|
+
...allViolations.providerSetupIssues.map(v => ({
|
|
2552
|
+
type: 'provider-setup',
|
|
2553
|
+
file: v.file,
|
|
2554
|
+
line: v.line,
|
|
2555
|
+
message: v.issue || v.reason,
|
|
2556
|
+
recommendation: v.recommendation
|
|
2557
|
+
})),
|
|
2558
|
+
...allViolations.viteConfigIssues.map(v => ({
|
|
2559
|
+
type: 'vite-config',
|
|
2560
|
+
file: v.file,
|
|
2561
|
+
line: v.line,
|
|
2562
|
+
message: v.issue,
|
|
2563
|
+
recommendation: v.recommendation
|
|
2564
|
+
})),
|
|
2565
|
+
...allViolations.routerSetupIssues.map(v => ({
|
|
2566
|
+
type: 'router-setup',
|
|
2567
|
+
file: v.file,
|
|
2568
|
+
line: v.line,
|
|
2569
|
+
message: v.issue,
|
|
2570
|
+
recommendation: v.recommendation
|
|
2571
|
+
}))
|
|
2572
|
+
];
|
|
2573
|
+
|
|
2574
|
+
const warnings = [
|
|
2575
|
+
...allViolations.customAuthCode.filter(v => v.severity === 'warning').map(v => ({
|
|
2576
|
+
type: 'custom-auth-code',
|
|
2577
|
+
file: v.file,
|
|
2578
|
+
line: v.line,
|
|
2579
|
+
message: `${v.type}: ${v.name} - ${v.reason}`,
|
|
2580
|
+
recommendation: v.replacement || 'Consider using pace-core APIs'
|
|
2581
|
+
})),
|
|
2582
|
+
...allViolations.directSupabaseAuth.map(v => ({
|
|
2583
|
+
type: 'direct-supabase-auth',
|
|
2584
|
+
file: v.file,
|
|
2585
|
+
line: v.line,
|
|
2586
|
+
message: `Direct Supabase auth usage: ${v.reason}`,
|
|
2587
|
+
recommendation: v.recommendation || 'Use useUnifiedAuth() instead'
|
|
2588
|
+
})),
|
|
2589
|
+
...allViolations.deprecatedSecureDataAccess.map(v => ({
|
|
2590
|
+
type: 'deprecated-api',
|
|
2591
|
+
file: v.file,
|
|
2592
|
+
line: v.line,
|
|
2593
|
+
message: `Deprecated API: ${v.method} - ${v.reason}`,
|
|
2594
|
+
recommendation: v.recommendation || 'Migrate to useSecureSupabase()'
|
|
2595
|
+
})),
|
|
2596
|
+
...allViolations.unnecessaryWrappers.map(v => ({
|
|
2597
|
+
type: 'unnecessary-wrapper',
|
|
2598
|
+
file: v.file,
|
|
2599
|
+
line: v.line,
|
|
2600
|
+
message: `Unnecessary wrapper: ${v.component} wraps ${v.wrappedComponent}`,
|
|
2601
|
+
recommendation: v.recommendation || 'Remove wrapper and use component directly'
|
|
2602
|
+
})),
|
|
2603
|
+
...allViolations.appDiscoveryIssues.filter(v => v.severity === 'warning').map(v => ({
|
|
2604
|
+
type: 'app-discovery',
|
|
2605
|
+
file: v.file,
|
|
2606
|
+
message: v.issue || v.reason,
|
|
2607
|
+
recommendation: v.recommendation
|
|
2608
|
+
}))
|
|
2609
|
+
];
|
|
2610
|
+
|
|
2611
|
+
const suggestions = [
|
|
2612
|
+
...allViolations.suggestions.map(v => ({
|
|
2613
|
+
type: 'suggestion',
|
|
2614
|
+
file: v.file,
|
|
2615
|
+
message: v.suggestion
|
|
2616
|
+
})),
|
|
2617
|
+
...allViolations.appDiscoveryIssues.filter(v => v.severity === 'info').map(v => ({
|
|
2618
|
+
type: 'app-discovery',
|
|
2619
|
+
file: v.file,
|
|
2620
|
+
message: v.issue || v.reason,
|
|
2621
|
+
recommendation: v.recommendation
|
|
2622
|
+
}))
|
|
2623
|
+
];
|
|
2624
|
+
|
|
2625
|
+
return {
|
|
2626
|
+
issues,
|
|
2627
|
+
warnings,
|
|
2628
|
+
suggestions,
|
|
2629
|
+
violations: allViolations
|
|
2630
|
+
};
|
|
2584
2631
|
}
|
|
2585
|
-
}
|
|
2586
|
-
|
|
2587
|
-
module.exports = { main, scanFile, generateReport };
|
|
2632
|
+
};
|
|
2588
2633
|
|
|
2634
|
+
module.exports = complianceCheck;
|