@jmruthers/pace-core 0.6.1 → 0.6.3
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/CHANGELOG.md +88 -10
- package/cursor-rules/00-pace-core-compliance.mdc +46 -87
- package/cursor-rules/01-standards-compliance.mdc +16 -47
- package/cursor-rules/02-project-structure.mdc +4 -4
- package/cursor-rules/03-solid-principles.mdc +45 -164
- package/cursor-rules/04-testing-standards.mdc +22 -69
- package/cursor-rules/05-bug-reports-and-features.mdc +2 -2
- package/cursor-rules/06-code-quality.mdc +42 -125
- package/cursor-rules/07-tech-stack-compliance.mdc +33 -128
- package/cursor-rules/08-markup-quality.mdc +452 -0
- package/cursor-rules/CHANGELOG.md +18 -0
- package/cursor-rules/README.md +2 -1
- package/dist/{AuthService-DjnJHDtC.d.ts → AuthService-Cb34EQs3.d.ts} +63 -1
- package/dist/{DataTable-CH1U5Tpy.d.ts → DataTable-BMRU8a1j.d.ts} +33 -1
- package/dist/{DataTable-DQ7RSOHE.js → DataTable-THFPBKTP.js} +12 -10
- package/dist/{PublicPageProvider-ce4xlHYA.d.ts → PublicPageProvider-DEMpysFR.d.ts} +394 -171
- package/dist/{UnifiedAuthProvider-185Ih4dj.d.ts → UnifiedAuthProvider-CKvHP1MK.d.ts} +30 -8
- package/dist/{UnifiedAuthProvider-ATAP5UTR.js → UnifiedAuthProvider-KAGUYQ4J.js} +5 -4
- package/dist/{api-N774RPUA.js → api-IAGWF3ZG.js} +10 -10
- package/dist/{audit-B5P6FFIR.js → audit-V53FV5AG.js} +2 -2
- package/dist/{chunk-JBKQ3SAO.js → chunk-2T2IG7T7.js} +107 -57
- package/dist/chunk-2T2IG7T7.js.map +1 -0
- package/dist/{chunk-3QRJFVBR.js → chunk-6SOIHG6Z.js} +1 -1
- package/dist/chunk-6SOIHG6Z.js.map +1 -0
- package/dist/{chunk-3XTALGJF.js → chunk-6Z7LTB3D.js} +69 -240
- package/dist/chunk-6Z7LTB3D.js.map +1 -0
- package/dist/{chunk-4ZC4GX36.js → chunk-CNCQDFLN.js} +199 -46
- package/dist/chunk-CNCQDFLN.js.map +1 -0
- package/dist/chunk-DGUM43GV.js +11 -0
- package/dist/{chunk-BYFSK72L.js → chunk-DWUBLJJM.js} +361 -187
- package/dist/chunk-DWUBLJJM.js.map +1 -0
- package/dist/{chunk-LXQLPRQ2.js → chunk-FFQEQTNW.js} +6 -8
- 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-4N5C5XZU.js → chunk-HFZBI76P.js} +4 -4
- package/dist/chunk-HFZBI76P.js.map +1 -0
- package/dist/{chunk-SQGMNID3.js → chunk-L4OXEN46.js} +4 -5
- package/dist/chunk-L4OXEN46.js.map +1 -0
- package/dist/{chunk-R77UEZ4E.js → chunk-M43Y4SSO.js} +1 -1
- package/dist/chunk-M43Y4SSO.js.map +1 -0
- package/dist/{chunk-I7PSE6JW.js → chunk-M7MPQISP.js} +3 -76
- package/dist/chunk-M7MPQISP.js.map +1 -0
- package/dist/chunk-PQBSKX33.js +7793 -0
- package/dist/chunk-PQBSKX33.js.map +1 -0
- package/dist/chunk-QRPVRXYT.js +226 -0
- package/dist/chunk-QRPVRXYT.js.map +1 -0
- package/dist/{chunk-KNC55RTG.js → chunk-RWEBCB47.js} +194 -416
- package/dist/chunk-RWEBCB47.js.map +1 -0
- package/dist/{chunk-XM25TVIE.js → chunk-YDQHOZNA.js} +843 -388
- package/dist/chunk-YDQHOZNA.js.map +1 -0
- package/dist/{chunk-GLK6VM3F.js → chunk-ZNIWI3UC.js} +739 -737
- package/dist/chunk-ZNIWI3UC.js.map +1 -0
- package/dist/components.d.ts +5 -5
- package/dist/components.js +18 -16
- package/dist/components.js.map +1 -1
- package/dist/contextValidator-3JNZKUTX.js +9 -0
- package/dist/contextValidator-3JNZKUTX.js.map +1 -0
- package/dist/eslint-rules/pace-core-compliance.cjs +106 -0
- package/dist/{functions-D_kgHktt.d.ts → functions-DHebl8-F.d.ts} +1 -1
- package/dist/hooks.d.ts +55 -122
- package/dist/hooks.js +10 -13
- package/dist/hooks.js.map +1 -1
- package/dist/index.d.ts +60 -13
- package/dist/index.js +30 -25
- package/dist/index.js.map +1 -1
- package/dist/providers.d.ts +21 -3
- package/dist/providers.js +4 -3
- package/dist/rbac/index.d.ts +210 -139
- package/dist/rbac/index.js +17 -13
- package/dist/styles/index.js +1 -1
- package/dist/theming/runtime.d.ts +1 -13
- package/dist/theming/runtime.js +2 -2
- 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/types.js +1 -1
- package/dist/{usePublicRouteParams-BJAlWfuJ.d.ts → usePublicRouteParams-i3qtoBgg.d.ts} +38 -17
- package/dist/utils.d.ts +4 -5
- package/dist/utils.js +17 -19
- package/dist/utils.js.map +1 -1
- package/docs/api/README.md +21 -17
- package/docs/api/modules.md +4191 -2967
- package/docs/architecture/database-schema-requirements.md +161 -0
- package/docs/components/context-selector.md +126 -0
- package/docs/core-concepts/rbac-system.md +3 -3
- package/docs/documentation-index.md +2 -4
- package/docs/getting-started/cursor-rules.md +2 -1
- package/docs/migration/DOCUMENTATION_STRUCTURE.md +441 -0
- package/docs/migration/MIGRATION_GUIDE.md +2 -24
- package/docs/migration/RBAC_SCOPE_MIGRATION.md +385 -0
- package/docs/migration/README.md +52 -6
- package/docs/migration/V0.5.190_TO_V0.6.1_MIGRATION.md +1153 -0
- package/docs/migration/database-changes-december-2025.md +3 -3
- package/docs/pace-mint-fix-auto-selection.md +218 -0
- package/docs/pace-mint-rbac-setup.md +391 -0
- 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/rbac/secure-client-protection.md +330 -0
- package/docs/standards/README.md +1 -0
- package/package.json +4 -3
- 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} +784 -685
- 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 +985 -0
- package/scripts/audit/core/checks/documentation.cjs +268 -0
- package/scripts/audit/core/checks/environment.cjs +116 -0
- package/scripts/audit/core/checks/error-handling.cjs +340 -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 +61 -936
- 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/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__/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 +14 -0
- package/src/components/Button/Button.tsx +22 -0
- package/src/components/Calendar/Calendar.tsx +8 -2
- 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/ContextSelector/ContextSelector.tsx +384 -0
- package/src/components/ContextSelector/index.ts +3 -0
- package/src/components/DataTable/DataTable.tsx +38 -4
- package/src/components/DataTable/__tests__/DataTableCore.test-setup.ts +5 -6
- package/src/components/DataTable/__tests__/pagination.modes.test.tsx +18 -4
- package/src/components/DataTable/__tests__/test-utils/sharedTestUtils.tsx +2 -3
- package/src/components/DataTable/components/AccessDeniedPage.tsx +16 -25
- package/src/components/DataTable/components/ActionButtons.tsx +10 -7
- package/src/components/DataTable/components/BulkOperationsDropdown.tsx +1 -1
- 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 +196 -554
- 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 +8 -0
- 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 +8 -0
- 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 +63 -851
- 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/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 +12 -0
- 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 +127 -33
- package/src/components/DataTable/hooks/useDataTableState.ts +35 -1
- package/src/components/DataTable/hooks/useEffectiveColumnOrder.ts +12 -0
- 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/Dialog/Dialog.tsx +31 -3
- package/src/components/ErrorBoundary/ErrorBoundary.test.tsx +180 -1
- package/src/components/ErrorBoundary/ErrorBoundary.tsx +45 -5
- package/src/components/ErrorBoundary/ErrorBoundaryContext.tsx +129 -0
- package/src/components/ErrorBoundary/index.ts +27 -2
- package/src/components/FileDisplay/FileDisplay.tsx +74 -28
- package/src/components/FileUpload/FileUpload.tsx +22 -2
- package/src/components/Footer/Footer.test.tsx +16 -16
- package/src/components/Footer/Footer.tsx +14 -11
- package/src/components/Form/Form.tsx +1 -0
- package/src/components/Header/Header.test.tsx +43 -73
- package/src/components/Header/Header.tsx +59 -49
- package/src/components/Input/Input.test.tsx +2 -2
- package/src/components/Input/Input.tsx +8 -4
- package/src/components/LoadingSpinner/LoadingSpinner.test.tsx +4 -4
- package/src/components/LoginForm/LoginForm.tsx +4 -0
- package/src/components/NavigationMenu/NavigationMenu.tsx +14 -513
- package/src/components/NavigationMenu/types.ts +56 -0
- package/src/components/NavigationMenu/useNavigationFiltering.ts +390 -0
- package/src/components/PaceAppLayout/PaceAppLayout.integration.test.tsx +10 -19
- package/src/components/PaceAppLayout/PaceAppLayout.performance.test.tsx +2 -2
- package/src/components/PaceAppLayout/PaceAppLayout.security.test.tsx +5 -5
- package/src/components/PaceAppLayout/PaceAppLayout.test.tsx +13 -11
- package/src/components/PaceAppLayout/PaceAppLayout.tsx +167 -44
- package/src/components/PaceAppLayout/README.md +14 -17
- package/src/components/PaceAppLayout/test-setup.tsx +3 -4
- package/src/components/PaceLoginPage/PaceLoginPage.tsx +3 -0
- package/src/components/PasswordChange/PasswordChangeForm.tsx +9 -0
- package/src/components/ProtectedRoute/ProtectedRoute.tsx +3 -9
- package/src/components/PublicLayout/PublicPageLayout.tsx +2 -5
- package/src/components/PublicLayout/PublicPageProvider.tsx +4 -0
- package/src/components/Select/Select.tsx +80 -434
- 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 +4 -5
- package/src/components/Switch/Switch.tsx +4 -4
- package/src/components/Tabs/Tabs.tsx +1 -1
- package/src/components/Toast/Toast.tsx +4 -0
- package/src/components/Tooltip/Tooltip.tsx +2 -2
- package/src/components/UserMenu/UserMenu.test.tsx +24 -11
- package/src/components/UserMenu/UserMenu.tsx +21 -18
- package/src/components/index.ts +7 -7
- package/src/eslint-rules/pace-core-compliance.cjs +106 -0
- package/src/hooks/__tests__/index.unit.test.ts +2 -5
- package/src/hooks/__tests__/useAppConfig.unit.test.ts +4 -98
- package/src/hooks/index.ts +1 -2
- package/src/hooks/public/usePublicEvent.ts +4 -0
- package/src/hooks/public/usePublicEventLogo.ts +4 -0
- package/src/hooks/public/usePublicFileDisplay.ts +4 -0
- package/src/hooks/public/usePublicRouteParams.ts +4 -0
- 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/useAppConfig.ts +15 -30
- package/src/hooks/useDebounce.ts +9 -0
- package/src/hooks/useEventTheme.ts +6 -0
- package/src/hooks/useFileDisplay.ts +81 -50
- package/src/hooks/useFileReference.ts +25 -7
- package/src/hooks/useFileUrl.ts +11 -1
- package/src/hooks/useFocusManagement.ts +14 -0
- package/src/hooks/useFocusTrap.ts +3 -0
- package/src/hooks/useInactivityTracker.ts +3 -0
- 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 +7 -0
- 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 +1 -1
- package/src/index.ts +6 -6
- package/src/providers/__tests__/OrganisationProvider.test.tsx +92 -70
- package/src/providers/services/AuthServiceProvider.tsx +35 -7
- package/src/providers/services/EventServiceProvider.tsx +51 -5
- package/src/providers/services/InactivityServiceProvider.tsx +18 -0
- package/src/providers/services/OrganisationServiceProvider.tsx +18 -0
- package/src/providers/services/UnifiedAuthProvider.tsx +126 -134
- package/src/providers/services/__tests__/AuthServiceProvider.integration.test.tsx +29 -13
- package/src/rbac/README.md +1 -1
- package/src/rbac/__tests__/adapters.comprehensive.test.tsx +1 -1
- package/src/rbac/__tests__/scenarios.user-role.test.tsx +4 -5
- package/src/rbac/adapters.tsx +12 -3
- package/src/rbac/api.test.ts +59 -51
- package/src/rbac/api.ts +246 -167
- package/src/rbac/components/NavigationProvider.tsx +4 -1
- package/src/rbac/components/PagePermissionGuard.tsx +185 -17
- package/src/rbac/components/RoleBasedRouter.tsx +5 -1
- package/src/rbac/components/SecureDataProvider.test.tsx +84 -49
- package/src/rbac/components/SecureDataProvider.tsx +20 -5
- 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/__tests__/useSecureSupabase.test.ts +32 -21
- 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 +377 -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 +64 -66
- package/src/rbac/hooks/usePermissions.ts +14 -995
- package/src/rbac/hooks/useRBAC.test.ts +1 -5
- package/src/rbac/hooks/useRBAC.ts +36 -37
- package/src/rbac/hooks/useResolvedScope.test.ts +120 -35
- package/src/rbac/hooks/useResolvedScope.ts +35 -40
- package/src/rbac/hooks/useResourcePermissions.test.ts +54 -18
- package/src/rbac/hooks/useResourcePermissions.ts +14 -4
- package/src/rbac/hooks/useSecureSupabase.ts +27 -7
- package/src/rbac/index.ts +7 -0
- package/src/rbac/permissions.ts +0 -30
- package/src/rbac/secureClient.test.ts +22 -18
- package/src/rbac/secureClient.ts +294 -68
- package/src/rbac/security.ts +0 -17
- package/src/rbac/types.ts +9 -0
- package/src/rbac/utils/__tests__/contextValidator.test.ts +64 -86
- package/src/rbac/utils/clientSecurity.ts +93 -0
- package/src/rbac/utils/contextValidator.ts +77 -168
- package/src/services/AuthService.ts +39 -7
- package/src/services/EventService.ts +186 -54
- package/src/services/OrganisationService.ts +81 -14
- package/src/services/__tests__/EventService.test.ts +1 -2
- package/src/services/base/BaseService.ts +3 -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/dynamic/dynamicUtils.ts +7 -4
- 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/dist/chunk-3QRJFVBR.js.map +0 -1
- package/dist/chunk-3XTALGJF.js.map +0 -1
- package/dist/chunk-4N5C5XZU.js.map +0 -1
- package/dist/chunk-4ZC4GX36.js.map +0 -1
- package/dist/chunk-7D4SUZUM.js +0 -38
- package/dist/chunk-BYFSK72L.js.map +0 -1
- package/dist/chunk-EXUD6RNJ.js +0 -451
- package/dist/chunk-EXUD6RNJ.js.map +0 -1
- package/dist/chunk-GLK6VM3F.js.map +0 -1
- package/dist/chunk-I7PSE6JW.js.map +0 -1
- package/dist/chunk-JBKQ3SAO.js.map +0 -1
- package/dist/chunk-KNC55RTG.js.map +0 -1
- package/dist/chunk-LXQLPRQ2.js.map +0 -1
- package/dist/chunk-R77UEZ4E.js.map +0 -1
- package/dist/chunk-SQGMNID3.js.map +0 -1
- package/dist/chunk-T33XF5ZC.js +0 -12922
- package/dist/chunk-T33XF5ZC.js.map +0 -1
- package/dist/chunk-XM25TVIE.js.map +0 -1
- package/docs/api/classes/ColumnFactory.md +0 -243
- package/docs/api/classes/ErrorBoundary.md +0 -144
- package/docs/api/classes/InvalidScopeError.md +0 -73
- package/docs/api/classes/Logger.md +0 -178
- package/docs/api/classes/MissingUserContextError.md +0 -66
- package/docs/api/classes/OrganisationContextRequiredError.md +0 -66
- package/docs/api/classes/PermissionDeniedError.md +0 -73
- package/docs/api/classes/RBACAuditManager.md +0 -297
- package/docs/api/classes/RBACCache.md +0 -322
- package/docs/api/classes/RBACEngine.md +0 -171
- package/docs/api/classes/RBACError.md +0 -76
- package/docs/api/classes/RBACNotInitializedError.md +0 -66
- package/docs/api/classes/SecureSupabaseClient.md +0 -160
- package/docs/api/classes/StorageUtils.md +0 -328
- package/docs/api/enums/FileCategory.md +0 -184
- package/docs/api/enums/LogLevel.md +0 -54
- package/docs/api/enums/RBACErrorCode.md +0 -228
- package/docs/api/enums/RPCFunction.md +0 -118
- package/docs/api/interfaces/AddressFieldProps.md +0 -241
- package/docs/api/interfaces/AddressFieldRef.md +0 -94
- package/docs/api/interfaces/AggregateConfig.md +0 -43
- package/docs/api/interfaces/AutocompleteOptions.md +0 -75
- package/docs/api/interfaces/AvatarProps.md +0 -128
- package/docs/api/interfaces/BadgeProps.md +0 -27
- package/docs/api/interfaces/ButtonProps.md +0 -53
- package/docs/api/interfaces/CalendarProps.md +0 -70
- package/docs/api/interfaces/CardProps.md +0 -66
- package/docs/api/interfaces/ColorPalette.md +0 -7
- package/docs/api/interfaces/ColorShade.md +0 -66
- package/docs/api/interfaces/ComplianceResult.md +0 -30
- package/docs/api/interfaces/DataAccessRecord.md +0 -96
- package/docs/api/interfaces/DataRecord.md +0 -11
- package/docs/api/interfaces/DataTableAction.md +0 -249
- package/docs/api/interfaces/DataTableColumn.md +0 -504
- package/docs/api/interfaces/DataTableProps.md +0 -625
- package/docs/api/interfaces/DataTableToolbarButton.md +0 -96
- package/docs/api/interfaces/DatabaseComplianceResult.md +0 -85
- package/docs/api/interfaces/DatabaseIssue.md +0 -41
- package/docs/api/interfaces/EmptyStateConfig.md +0 -61
- package/docs/api/interfaces/EnhancedNavigationMenuProps.md +0 -235
- package/docs/api/interfaces/EventAppRoleData.md +0 -71
- package/docs/api/interfaces/ExportColumn.md +0 -90
- package/docs/api/interfaces/ExportOptions.md +0 -126
- package/docs/api/interfaces/FileDisplayProps.md +0 -249
- package/docs/api/interfaces/FileMetadata.md +0 -129
- package/docs/api/interfaces/FileReference.md +0 -118
- package/docs/api/interfaces/FileSizeLimits.md +0 -7
- package/docs/api/interfaces/FileUploadOptions.md +0 -139
- package/docs/api/interfaces/FileUploadProps.md +0 -293
- package/docs/api/interfaces/FooterProps.md +0 -105
- package/docs/api/interfaces/FormFieldProps.md +0 -166
- package/docs/api/interfaces/FormProps.md +0 -113
- package/docs/api/interfaces/GrantEventAppRoleParams.md +0 -122
- package/docs/api/interfaces/InactivityWarningModalProps.md +0 -115
- package/docs/api/interfaces/InputProps.md +0 -53
- package/docs/api/interfaces/LabelProps.md +0 -107
- package/docs/api/interfaces/LoggerConfig.md +0 -62
- package/docs/api/interfaces/LoginFormProps.md +0 -184
- package/docs/api/interfaces/NavigationAccessRecord.md +0 -107
- package/docs/api/interfaces/NavigationContextType.md +0 -164
- package/docs/api/interfaces/NavigationGuardProps.md +0 -139
- package/docs/api/interfaces/NavigationItem.md +0 -120
- package/docs/api/interfaces/NavigationMenuProps.md +0 -221
- package/docs/api/interfaces/NavigationProviderProps.md +0 -117
- package/docs/api/interfaces/Organisation.md +0 -140
- package/docs/api/interfaces/OrganisationContextType.md +0 -388
- package/docs/api/interfaces/OrganisationMembership.md +0 -140
- package/docs/api/interfaces/OrganisationProviderProps.md +0 -76
- package/docs/api/interfaces/OrganisationSecurityError.md +0 -62
- package/docs/api/interfaces/PaceAppLayoutProps.md +0 -406
- package/docs/api/interfaces/PaceLoginPageProps.md +0 -47
- package/docs/api/interfaces/PageAccessRecord.md +0 -85
- package/docs/api/interfaces/PagePermissionContextType.md +0 -140
- package/docs/api/interfaces/PagePermissionGuardProps.md +0 -153
- package/docs/api/interfaces/PagePermissionProviderProps.md +0 -119
- package/docs/api/interfaces/PaletteData.md +0 -41
- package/docs/api/interfaces/ParsedAddress.md +0 -120
- package/docs/api/interfaces/PermissionEnforcerProps.md +0 -153
- package/docs/api/interfaces/ProgressProps.md +0 -42
- package/docs/api/interfaces/ProtectedRouteProps.md +0 -97
- package/docs/api/interfaces/PublicPageFooterProps.md +0 -112
- package/docs/api/interfaces/PublicPageHeaderProps.md +0 -125
- package/docs/api/interfaces/PublicPageLayoutProps.md +0 -198
- package/docs/api/interfaces/QuickFix.md +0 -52
- package/docs/api/interfaces/RBACAccessValidateParams.md +0 -52
- package/docs/api/interfaces/RBACAccessValidateResult.md +0 -41
- package/docs/api/interfaces/RBACAuditLogParams.md +0 -85
- package/docs/api/interfaces/RBACAuditLogResult.md +0 -52
- package/docs/api/interfaces/RBACConfig.md +0 -133
- package/docs/api/interfaces/RBACContext.md +0 -52
- package/docs/api/interfaces/RBACLogger.md +0 -112
- package/docs/api/interfaces/RBACPageAccessCheckParams.md +0 -74
- package/docs/api/interfaces/RBACPerformanceMetrics.md +0 -138
- package/docs/api/interfaces/RBACPermissionCheckParams.md +0 -74
- package/docs/api/interfaces/RBACPermissionCheckResult.md +0 -52
- package/docs/api/interfaces/RBACPermissionsGetParams.md +0 -63
- package/docs/api/interfaces/RBACPermissionsGetResult.md +0 -63
- package/docs/api/interfaces/RBACResult.md +0 -58
- package/docs/api/interfaces/RBACRoleGrantParams.md +0 -63
- package/docs/api/interfaces/RBACRoleGrantResult.md +0 -52
- package/docs/api/interfaces/RBACRoleRevokeParams.md +0 -63
- package/docs/api/interfaces/RBACRoleRevokeResult.md +0 -52
- package/docs/api/interfaces/RBACRoleValidateParams.md +0 -52
- package/docs/api/interfaces/RBACRoleValidateResult.md +0 -63
- package/docs/api/interfaces/RBACRolesListParams.md +0 -52
- package/docs/api/interfaces/RBACRolesListResult.md +0 -74
- package/docs/api/interfaces/RBACSessionTrackParams.md +0 -74
- package/docs/api/interfaces/RBACSessionTrackResult.md +0 -52
- package/docs/api/interfaces/ResourcePermissions.md +0 -155
- package/docs/api/interfaces/RevokeEventAppRoleParams.md +0 -100
- package/docs/api/interfaces/RoleBasedRouterContextType.md +0 -151
- package/docs/api/interfaces/RoleBasedRouterProps.md +0 -156
- package/docs/api/interfaces/RoleManagementResult.md +0 -52
- package/docs/api/interfaces/RouteAccessRecord.md +0 -107
- package/docs/api/interfaces/RouteConfig.md +0 -134
- package/docs/api/interfaces/RuntimeComplianceResult.md +0 -55
- package/docs/api/interfaces/SecureDataContextType.md +0 -168
- package/docs/api/interfaces/SecureDataProviderProps.md +0 -132
- package/docs/api/interfaces/SessionRestorationLoaderProps.md +0 -34
- package/docs/api/interfaces/SetupIssue.md +0 -41
- package/docs/api/interfaces/StorageConfig.md +0 -41
- package/docs/api/interfaces/StorageFileInfo.md +0 -74
- package/docs/api/interfaces/StorageFileMetadata.md +0 -151
- package/docs/api/interfaces/StorageListOptions.md +0 -99
- package/docs/api/interfaces/StorageListResult.md +0 -41
- package/docs/api/interfaces/StorageUploadOptions.md +0 -101
- package/docs/api/interfaces/StorageUploadResult.md +0 -63
- package/docs/api/interfaces/StorageUrlOptions.md +0 -60
- package/docs/api/interfaces/StyleImport.md +0 -19
- package/docs/api/interfaces/SwitchProps.md +0 -34
- package/docs/api/interfaces/TabsContentProps.md +0 -9
- package/docs/api/interfaces/TabsListProps.md +0 -9
- package/docs/api/interfaces/TabsProps.md +0 -9
- package/docs/api/interfaces/TabsTriggerProps.md +0 -50
- package/docs/api/interfaces/TextareaProps.md +0 -53
- package/docs/api/interfaces/ToastActionElement.md +0 -9
- package/docs/api/interfaces/ToastProps.md +0 -9
- package/docs/api/interfaces/UnifiedAuthContextType.md +0 -820
- package/docs/api/interfaces/UnifiedAuthProviderProps.md +0 -171
- package/docs/api/interfaces/UseFormDialogOptions.md +0 -62
- package/docs/api/interfaces/UseFormDialogReturn.md +0 -117
- package/docs/api/interfaces/UseInactivityTrackerOptions.md +0 -136
- package/docs/api/interfaces/UseInactivityTrackerReturn.md +0 -123
- package/docs/api/interfaces/UsePublicEventLogoOptions.md +0 -87
- package/docs/api/interfaces/UsePublicEventLogoReturn.md +0 -81
- package/docs/api/interfaces/UsePublicEventOptions.md +0 -34
- package/docs/api/interfaces/UsePublicEventReturn.md +0 -68
- package/docs/api/interfaces/UsePublicFileDisplayOptions.md +0 -47
- package/docs/api/interfaces/UsePublicFileDisplayReturn.md +0 -120
- package/docs/api/interfaces/UsePublicRouteParamsReturn.md +0 -94
- package/docs/api/interfaces/UseResolvedScopeOptions.md +0 -47
- package/docs/api/interfaces/UseResolvedScopeReturn.md +0 -47
- package/docs/api/interfaces/UseResourcePermissionsOptions.md +0 -34
- package/docs/api/interfaces/UserEventAccess.md +0 -118
- package/docs/api/interfaces/UserMenuProps.md +0 -86
- package/docs/api/interfaces/UserProfile.md +0 -63
- package/docs/migration/quick-migration-guide.md +0 -356
- package/docs/migration/service-architecture.md +0 -281
- package/src/components/EventSelector/EventSelector.test.tsx +0 -720
- package/src/components/EventSelector/EventSelector.tsx +0 -420
- package/src/components/EventSelector/index.ts +0 -3
- package/src/components/OrganisationSelector/OrganisationSelector.test.tsx +0 -784
- package/src/components/OrganisationSelector/OrganisationSelector.tsx +0 -324
- package/src/components/OrganisationSelector/index.ts +0 -9
- package/src/hooks/__tests__/useSecureDataAccess.unit.test.tsx +0 -680
- package/src/hooks/useSecureDataAccess.test.ts +0 -559
- package/src/hooks/useSecureDataAccess.ts +0 -681
- /package/dist/{DataTable-DQ7RSOHE.js.map → DataTable-THFPBKTP.js.map} +0 -0
- /package/dist/{UnifiedAuthProvider-ATAP5UTR.js.map → UnifiedAuthProvider-KAGUYQ4J.js.map} +0 -0
- /package/dist/{api-N774RPUA.js.map → api-IAGWF3ZG.js.map} +0 -0
- /package/dist/{audit-B5P6FFIR.js.map → audit-V53FV5AG.js.map} +0 -0
- /package/dist/{chunk-7D4SUZUM.js.map → chunk-DGUM43GV.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/docs/migration/{REACT_19_MIGRATION.md → V0.6.0_REACT_19_MIGRATION.md} +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,7 @@ 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);
|
|
387
477
|
|
|
388
478
|
// Normalize path for cross-platform compatibility (handle both forward and backslash paths)
|
|
389
479
|
const normalizedPath = relativePath.replace(/\\/g, '/');
|
|
@@ -392,6 +482,28 @@ function scanFile(filePath, manifest) {
|
|
|
392
482
|
// Direct Supabase auth calls are the correct approach in Edge Functions
|
|
393
483
|
const isEdgeFunction = normalizedPath.includes('supabase/functions/');
|
|
394
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
|
+
}
|
|
506
|
+
|
|
395
507
|
// Check for restricted imports
|
|
396
508
|
manifest.restrictedImports.forEach(({ module, reason }) => {
|
|
397
509
|
const importPattern = new RegExp(`from\\s+['"]${module.replace(/[.*+?^${}()|[\]\\]/g, '\\$&')}['"]`, 'g');
|
|
@@ -485,6 +597,11 @@ function scanFile(filePath, manifest) {
|
|
|
485
597
|
// RBAC/Auth Compliance Checks
|
|
486
598
|
// ============================================
|
|
487
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
|
+
|
|
488
605
|
// Check for custom auth/rbac/permission code that doesn't import from pace-core
|
|
489
606
|
const authRbacPatterns = [
|
|
490
607
|
// Custom auth hooks
|
|
@@ -515,11 +632,6 @@ function scanFile(filePath, manifest) {
|
|
|
515
632
|
{ pattern: /export\s+(default\s+)?(function|const)\s+isPermitted\s*[=\(]/g, name: 'isPermitted', type: 'util' }
|
|
516
633
|
];
|
|
517
634
|
|
|
518
|
-
// Check if file imports from pace-core for auth/rbac
|
|
519
|
-
const hasPaceCoreImport = /from\s+['"]@jmruthers\/pace-core/.test(content) ||
|
|
520
|
-
/from\s+['"]@jmruthers\/pace-core\/rbac/.test(content) ||
|
|
521
|
-
/from\s+['"]@jmruthers\/pace-core\/providers/.test(content);
|
|
522
|
-
|
|
523
635
|
authRbacPatterns.forEach(({ pattern, name, type, replacement }) => {
|
|
524
636
|
// Create a new regex instance to avoid state issues
|
|
525
637
|
const testPattern = new RegExp(pattern.source, pattern.flags);
|
|
@@ -537,7 +649,7 @@ function scanFile(filePath, manifest) {
|
|
|
537
649
|
].includes(name);
|
|
538
650
|
|
|
539
651
|
// Check if the hook uses pace-core RBAC APIs (use a fresh regex to avoid state issues)
|
|
540
|
-
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/;
|
|
541
653
|
const usesPaceCoreRBAC = isCustomRBACHook && rbacApiPattern.test(content);
|
|
542
654
|
|
|
543
655
|
// Check for @pace-core-compliant comment (use a fresh regex)
|
|
@@ -677,6 +789,13 @@ function scanFile(filePath, manifest) {
|
|
|
677
789
|
{ pattern: /\.auth\.onAuthStateChange\s*\(/g, method: 'onAuthStateChange' }
|
|
678
790
|
];
|
|
679
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);
|
|
798
|
+
|
|
680
799
|
// Skip all auth checks for Edge Functions - they cannot use React hooks
|
|
681
800
|
if (isEdgeFunction) {
|
|
682
801
|
// Edge Functions use service role client or direct auth calls - correct pattern
|
|
@@ -696,12 +815,6 @@ function scanFile(filePath, manifest) {
|
|
|
696
815
|
{ pattern: /\w+\.auth\.getUser\s*\(/g, method: 'getUser', specific: false },
|
|
697
816
|
{ pattern: /\w+\.auth\.getSession\s*\(/g, method: 'getSession', specific: false }
|
|
698
817
|
];
|
|
699
|
-
|
|
700
|
-
// Check if file actually uses useUnifiedAuth hook (not just imports it)
|
|
701
|
-
const usesUnifiedAuthHook = /useUnifiedAuth\s*\(/.test(content);
|
|
702
|
-
const hasUnifiedAuthImport = /UnifiedAuthProvider/.test(content) ||
|
|
703
|
-
/useUnifiedAuth/.test(content) ||
|
|
704
|
-
/from\s+['"]@jmruthers\/pace-core\/providers/.test(content);
|
|
705
818
|
|
|
706
819
|
// Check for usage of useCurrentUser hook (even if imported from local file)
|
|
707
820
|
// This catches both local imports and direct usage
|
|
@@ -885,11 +998,11 @@ function scanFile(filePath, manifest) {
|
|
|
885
998
|
{ name: 'rbac_apps', type: 'config', recommendation: 'For admin operations, use useSecureSupabase. For application use, use pace-core RBAC APIs' },
|
|
886
999
|
{ name: 'rbac_app_pages', type: 'config', recommendation: 'For admin operations, use useSecureSupabase. For application use, use pace-core permission management APIs or PagePermissionGuard' },
|
|
887
1000
|
{ name: 'rbac_page_permissions', type: 'config', recommendation: 'For admin operations, use useSecureSupabase. For application use, use pace-core permission management APIs or PagePermissionGuard' },
|
|
888
|
-
{ 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' },
|
|
889
1002
|
// User data tables - acceptable to query but must use secure methods
|
|
890
|
-
{ name: 'rbac_user_profiles', type: 'user_data', recommendation: 'Use useSecureSupabase
|
|
891
|
-
{ name: 'rbac_user_login_history', type: 'audit', recommendation: 'Use useSecureSupabase
|
|
892
|
-
{ 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' }
|
|
893
1006
|
];
|
|
894
1007
|
|
|
895
1008
|
// Detect admin/management context
|
|
@@ -914,26 +1027,19 @@ function scanFile(filePath, manifest) {
|
|
|
914
1027
|
/useRBAC/.test(content) ||
|
|
915
1028
|
/usePermissions/.test(content) ||
|
|
916
1029
|
/useSecureSupabase/.test(content) ||
|
|
917
|
-
/useSecureDataAccess/.test(content) ||
|
|
918
1030
|
/PagePermissionGuard/.test(content);
|
|
919
1031
|
|
|
920
|
-
// Check if file
|
|
921
|
-
const usesSecureDataAccess = /useSecureDataAccess/.test(content);
|
|
922
|
-
|
|
923
|
-
// Check if file destructures secure methods from useSecureDataAccess
|
|
1032
|
+
// Check if file destructures secure methods (deprecated secureQuery/secureInsert/etc from useSecureDataAccess)
|
|
924
1033
|
const hasSecureMethods = /(const|let)\s*\{[^}]*secure(Query|Update|Insert|Delete)/.test(content) ||
|
|
925
1034
|
/secure(Query|Update|Insert|Delete)\s*\(/.test(content);
|
|
926
1035
|
|
|
927
1036
|
// First, identify all variables assigned from secure hooks
|
|
928
|
-
// Match patterns like: const supabase = useSecureSupabase();
|
|
1037
|
+
// Match patterns like: const supabase = useSecureSupabase();
|
|
929
1038
|
// Also detect fromSupabaseClient and wrapper patterns
|
|
930
1039
|
const secureVariablePatterns = [
|
|
931
1040
|
/const\s+(\w+)\s*=\s*useSecureSupabase\s*\(/g,
|
|
932
|
-
/const\s+(\w+)\s*=\s*useSecureDataAccess\s*\(/g,
|
|
933
1041
|
/let\s+(\w+)\s*=\s*useSecureSupabase\s*\(/g,
|
|
934
|
-
/let\s+(\w+)\s*=\s*useSecureDataAccess\s*\(/g,
|
|
935
1042
|
/(\w+)\s*=\s*useSecureSupabase\s*\(/g,
|
|
936
|
-
/(\w+)\s*=\s*useSecureDataAccess\s*\(/g,
|
|
937
1043
|
// Detect fromSupabaseClient usage
|
|
938
1044
|
/const\s+(\w+)\s*=\s*fromSupabaseClient\s*\(/g,
|
|
939
1045
|
/let\s+(\w+)\s*=\s*fromSupabaseClient\s*\(/g,
|
|
@@ -1059,12 +1165,55 @@ function scanFile(filePath, manifest) {
|
|
|
1059
1165
|
const isConfigTable = type === 'config';
|
|
1060
1166
|
|
|
1061
1167
|
// Check if the variable comes from a secure hook
|
|
1062
|
-
// 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
|
|
1063
1169
|
// Escape special regex characters in variable name and use multiline flag to handle newlines
|
|
1064
1170
|
const escapedVarName = variableName ? variableName.replace(/[.*+?^${}()|[\]\\]/g, '\\$&') : '';
|
|
1065
|
-
|
|
1066
|
-
|
|
1067
|
-
|
|
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);
|
|
1068
1217
|
|
|
1069
1218
|
// Determine severity based on context
|
|
1070
1219
|
let severity = 'error';
|
|
@@ -1076,7 +1225,7 @@ function scanFile(filePath, manifest) {
|
|
|
1076
1225
|
continue;
|
|
1077
1226
|
} else {
|
|
1078
1227
|
severity = 'error';
|
|
1079
|
-
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.`;
|
|
1080
1229
|
}
|
|
1081
1230
|
} else if (isConfigTable) {
|
|
1082
1231
|
if (isUsingSecureVariable) {
|
|
@@ -1086,7 +1235,7 @@ function scanFile(filePath, manifest) {
|
|
|
1086
1235
|
} else if (isAdminContext) {
|
|
1087
1236
|
// Admin operations without secure methods - warning
|
|
1088
1237
|
severity = 'warning';
|
|
1089
|
-
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.`;
|
|
1090
1239
|
} else {
|
|
1091
1240
|
// Not admin context and not using secure methods - error
|
|
1092
1241
|
severity = 'error';
|
|
@@ -1148,8 +1297,8 @@ function scanFile(filePath, manifest) {
|
|
|
1148
1297
|
} else if (isConfigTable) {
|
|
1149
1298
|
// Config tables using secure methods - acceptable for admin operations
|
|
1150
1299
|
// If using secureQuery/secureUpdate/etc., it's already using secure methods
|
|
1151
|
-
if (isAdminContext
|
|
1152
|
-
// Config tables in admin context
|
|
1300
|
+
if (isAdminContext) {
|
|
1301
|
+
// Config tables in admin context - acceptable
|
|
1153
1302
|
continue; // Skip - this is correct usage for admin operations
|
|
1154
1303
|
} else {
|
|
1155
1304
|
severity = 'error';
|
|
@@ -1225,10 +1374,9 @@ function scanFile(filePath, manifest) {
|
|
|
1225
1374
|
// Check if using secure variable (check both set and direct pattern match)
|
|
1226
1375
|
// Escape special regex characters in variable name and use multiline flag to handle newlines
|
|
1227
1376
|
const escapedVarName = variableName ? variableName.replace(/[.*+?^${}()|[\]\\]/g, '\\$&') : '';
|
|
1228
|
-
// Check if variable is declared with useSecureSupabase
|
|
1377
|
+
// Check if variable is declared with useSecureSupabase
|
|
1229
1378
|
const isDeclaredSecure = (variableName && secureVariables.has(variableName)) ||
|
|
1230
|
-
(variableName && new RegExp(`(const|let)\\s+${escapedVarName}\\s*=\\s*useSecureSupabase\\s*\\(`, 'm').test(content))
|
|
1231
|
-
(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));
|
|
1232
1380
|
// Check if variable is passed as parameter with useSecureSupabase type annotation
|
|
1233
1381
|
// Look for the parameter in function signatures (check both beforeMatch and full content)
|
|
1234
1382
|
const isParameterSecure = variableName && (
|
|
@@ -1270,12 +1418,12 @@ function scanFile(filePath, manifest) {
|
|
|
1270
1418
|
let severity = 'error';
|
|
1271
1419
|
let reason = '';
|
|
1272
1420
|
|
|
1273
|
-
if (isConfigTable &&
|
|
1421
|
+
if (isConfigTable && isAdminContext && (isUsingSecureVariable || hasSecureMethods)) {
|
|
1274
1422
|
// Admin operations with secure methods - acceptable, skip
|
|
1275
1423
|
continue;
|
|
1276
1424
|
} else if (isConfigTable && isAdminContext && !isUsingSecureVariable && !hasSecureMethods) {
|
|
1277
1425
|
severity = 'warning';
|
|
1278
|
-
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.`;
|
|
1279
1427
|
} else if (isConfigTable && !isAdminContext) {
|
|
1280
1428
|
severity = 'error';
|
|
1281
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.`;
|
|
@@ -1285,7 +1433,7 @@ function scanFile(filePath, manifest) {
|
|
|
1285
1433
|
continue;
|
|
1286
1434
|
}
|
|
1287
1435
|
severity = 'error';
|
|
1288
|
-
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.`;
|
|
1289
1437
|
} else {
|
|
1290
1438
|
severity = 'error';
|
|
1291
1439
|
reason = `Direct query to RBAC table '${tableName}' detected. Use pace-core RBAC hooks, RPC functions, or useSecureSupabase instead.`;
|
|
@@ -1470,6 +1618,61 @@ function scanFile(filePath, manifest) {
|
|
|
1470
1618
|
// For .from() patterns, match[1] is the CRUD method; for secure* patterns, operation is already set
|
|
1471
1619
|
const crudMethod = method === 'from' ? match[1] : operation;
|
|
1472
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
|
+
|
|
1473
1676
|
// Skip if in a line comment
|
|
1474
1677
|
const lineStart = content.lastIndexOf('\n', matchIndex) + 1;
|
|
1475
1678
|
const lineUpToMatch = content.substring(lineStart, matchIndex);
|
|
@@ -1481,6 +1684,16 @@ function scanFile(filePath, manifest) {
|
|
|
1481
1684
|
let severity = 'error';
|
|
1482
1685
|
let example = '';
|
|
1483
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
|
+
|
|
1484
1697
|
if (isRole) {
|
|
1485
1698
|
// Role mutations should use RPC functions
|
|
1486
1699
|
const isGrant = crudMethod === 'insert' || crudMethod === 'upsert';
|
|
@@ -1503,24 +1716,25 @@ function scanFile(filePath, manifest) {
|
|
|
1503
1716
|
if (method && method.startsWith('secure')) {
|
|
1504
1717
|
// Using secureInsert/secureUpdate/secureDelete - this is correct, don't flag
|
|
1505
1718
|
continue;
|
|
1506
|
-
} else if (isAdminContext
|
|
1507
|
-
// Admin context
|
|
1719
|
+
} else if (isAdminContext) {
|
|
1720
|
+
// Admin context - check if using secure methods
|
|
1508
1721
|
// If the operation uses secureQuery/secureUpdate/etc, it's already handled above
|
|
1509
1722
|
// This case is for direct .from() calls in admin context
|
|
1510
1723
|
severity = 'warning';
|
|
1511
|
-
reason = `Admin operation (${crudMethod}) on configuration table '${table}' detected. Ensure you're using useSecureSupabase
|
|
1512
|
-
replacement = 'Use useSecureSupabase
|
|
1513
|
-
} else if (isAdminContext) {
|
|
1514
|
-
severity = 'warning';
|
|
1515
|
-
reason = `Admin operation (${crudMethod}) on configuration table '${table}' detected. Ensure you're using useSecureSupabase or useSecureDataAccess for security.`;
|
|
1516
|
-
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';
|
|
1517
1726
|
} else {
|
|
1518
1727
|
reason = `Direct ${crudMethod} operation on configuration table '${table}' detected. These are system configuration tables. For admin operations, use useSecureSupabase.`;
|
|
1519
|
-
replacement = 'Use useSecureSupabase
|
|
1728
|
+
replacement = 'Use useSecureSupabase from pace-core for admin operations';
|
|
1520
1729
|
}
|
|
1521
1730
|
} else if (isUserData) {
|
|
1522
|
-
|
|
1523
|
-
|
|
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';
|
|
1524
1738
|
} else {
|
|
1525
1739
|
reason = `Direct ${crudMethod} operation on '${table}' detected. Use pace-core permission management APIs or documented RPC functions instead.`;
|
|
1526
1740
|
replacement = 'pace-core permission management APIs or RPC functions';
|
|
@@ -1629,7 +1843,7 @@ function scanFile(filePath, manifest) {
|
|
|
1629
1843
|
type,
|
|
1630
1844
|
file: relativePath,
|
|
1631
1845
|
line: getLineNumber(content, hookStartMatch[0]),
|
|
1632
|
-
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.`,
|
|
1633
1847
|
replacement,
|
|
1634
1848
|
severity: 'error'
|
|
1635
1849
|
});
|
|
@@ -1640,7 +1854,7 @@ function scanFile(filePath, manifest) {
|
|
|
1640
1854
|
type,
|
|
1641
1855
|
file: relativePath,
|
|
1642
1856
|
line: getLineNumber(content, hookStartMatch[0]),
|
|
1643
|
-
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.`,
|
|
1644
1858
|
replacement,
|
|
1645
1859
|
severity: 'error'
|
|
1646
1860
|
});
|
|
@@ -1651,7 +1865,7 @@ function scanFile(filePath, manifest) {
|
|
|
1651
1865
|
type,
|
|
1652
1866
|
file: relativePath,
|
|
1653
1867
|
line: getLineNumber(content, hookStartMatch[0]),
|
|
1654
|
-
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.`,
|
|
1655
1869
|
replacement,
|
|
1656
1870
|
severity: 'error'
|
|
1657
1871
|
});
|
|
@@ -1708,7 +1922,7 @@ function scanFile(filePath, manifest) {
|
|
|
1708
1922
|
const operatesOnPagePermissions = /rbac_page_permissions/.test(content);
|
|
1709
1923
|
const operatesOnRoleTables = /rbac_(organisation|event_app|global)_roles/.test(content);
|
|
1710
1924
|
const operatesOnConfigTables = /rbac_apps|rbac_app_pages/.test(content);
|
|
1711
|
-
const usesSecureMethods = /
|
|
1925
|
+
const usesSecureMethods = /useSecureSupabase|secureQuery|secureUpdate|secureInsert|secureDelete/.test(content);
|
|
1712
1926
|
|
|
1713
1927
|
// Only flag as permission management if:
|
|
1714
1928
|
// 1. It's explicitly a permission management component AND operates on permission/role tables
|
|
@@ -1737,8 +1951,8 @@ function scanFile(filePath, manifest) {
|
|
|
1737
1951
|
type: 'configuration management component',
|
|
1738
1952
|
file: relativePath,
|
|
1739
1953
|
line: getLineNumber(content, content.match(componentPattern)[0]),
|
|
1740
|
-
reason: `Configuration management component '${name}' detected. Ensure you're using
|
|
1741
|
-
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',
|
|
1742
1956
|
severity: 'warning'
|
|
1743
1957
|
});
|
|
1744
1958
|
}
|
|
@@ -1769,7 +1983,10 @@ function scanFile(filePath, manifest) {
|
|
|
1769
1983
|
}
|
|
1770
1984
|
|
|
1771
1985
|
// Check Vite configuration
|
|
1772
|
-
|
|
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)$/)) {
|
|
1773
1990
|
const viteIssues = scanViteConfig(filePath, content, relativePath);
|
|
1774
1991
|
violations.viteConfigIssues.push(...viteIssues);
|
|
1775
1992
|
}
|
|
@@ -1964,644 +2181,526 @@ function scanFile(filePath, manifest) {
|
|
|
1964
2181
|
}
|
|
1965
2182
|
}
|
|
1966
2183
|
|
|
1967
|
-
|
|
1968
|
-
|
|
1969
|
-
|
|
1970
|
-
//
|
|
1971
|
-
|
|
1972
|
-
|
|
1973
|
-
|
|
1974
|
-
// Check if file imports from pace-core
|
|
1975
|
-
const paceCoreImportPattern = /import\s+{([^}]+)}\s+from\s+['"]@jmruthers\/pace-core['"]/;
|
|
1976
|
-
const paceCoreImportMatch = content.match(paceCoreImportPattern);
|
|
1977
|
-
|
|
1978
|
-
// Extract imported pace-core component names
|
|
1979
|
-
let importedPaceCoreComponents = [];
|
|
1980
|
-
if (paceCoreImportMatch) {
|
|
1981
|
-
importedPaceCoreComponents = (paceCoreImportMatch[1] || '')
|
|
1982
|
-
.split(',')
|
|
1983
|
-
.map(name => name.trim().replace(/\s+as\s+\w+/, '')) // Remove aliases
|
|
1984
|
-
.filter(name => manifest.components.includes(name));
|
|
1985
|
-
}
|
|
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
|
|
1986
2190
|
|
|
1987
|
-
//
|
|
1988
|
-
|
|
1989
|
-
|
|
1990
|
-
|
|
1991
|
-
|
|
1992
|
-
const
|
|
1993
|
-
const defaultImport = importMatch[2]; // Component
|
|
1994
|
-
const modulePath = importMatch[3];
|
|
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);
|
|
1995
2197
|
|
|
1996
|
-
|
|
1997
|
-
|
|
1998
|
-
|
|
1999
|
-
|
|
2000
|
-
});
|
|
2001
|
-
}
|
|
2002
|
-
if (defaultImport) {
|
|
2003
|
-
allImports.push({ name: defaultImport, module: modulePath });
|
|
2004
|
-
}
|
|
2005
|
-
}
|
|
2006
|
-
|
|
2007
|
-
// Find exported component definitions
|
|
2008
|
-
// Match: export (default)? (function|const) ComponentName = ...
|
|
2009
|
-
const componentPattern = /export\s+(default\s+)?(function|const)\s+(\w+)\s*[=\(]/g;
|
|
2010
|
-
const componentMatches = [...content.matchAll(componentPattern)];
|
|
2011
|
-
|
|
2012
|
-
componentMatches.forEach(match => {
|
|
2013
|
-
const componentName = match[3];
|
|
2014
|
-
const matchIndex = match.index;
|
|
2198
|
+
// Check for createClient usage
|
|
2199
|
+
const createClientUsagePattern = /createClient\s*\(/g;
|
|
2200
|
+
const createClientMatches = content.match(createClientUsagePattern);
|
|
2201
|
+
const hasCreateClientUsage = createClientMatches && createClientMatches.length > 0;
|
|
2015
2202
|
|
|
2016
|
-
//
|
|
2017
|
-
|
|
2018
|
-
|
|
2019
|
-
return;
|
|
2020
|
-
}
|
|
2203
|
+
// Check if file uses useSecureSupabase (correct usage)
|
|
2204
|
+
const usesSecureSupabase = /useSecureSupabase/.test(content) ||
|
|
2205
|
+
/from\s+['"]@jmruthers\/pace-core\/rbac['"]/.test(content);
|
|
2021
2206
|
|
|
2022
|
-
// Find
|
|
2023
|
-
|
|
2024
|
-
|
|
2025
|
-
let
|
|
2026
|
-
|
|
2027
|
-
|
|
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]);
|
|
2214
|
+
}
|
|
2215
|
+
}
|
|
2028
2216
|
|
|
2029
|
-
|
|
2030
|
-
|
|
2031
|
-
|
|
2032
|
-
|
|
2033
|
-
|
|
2034
|
-
|
|
2035
|
-
|
|
2036
|
-
|
|
2037
|
-
|
|
2038
|
-
|
|
2039
|
-
|
|
2040
|
-
|
|
2041
|
-
|
|
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;
|
|
2228
|
+
}
|
|
2229
|
+
|
|
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);
|
|
2234
|
+
|
|
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'));
|
|
2259
|
+
|
|
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
|
+
}
|
|
2042
2280
|
}
|
|
2043
2281
|
}
|
|
2044
2282
|
}
|
|
2045
2283
|
|
|
2046
|
-
if
|
|
2047
|
-
|
|
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);
|
|
2296
|
+
|
|
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
|
+
}
|
|
2307
|
+
}
|
|
2048
2308
|
}
|
|
2049
2309
|
|
|
2050
|
-
|
|
2051
|
-
|
|
2052
|
-
|
|
2053
|
-
|
|
2054
|
-
|
|
2055
|
-
|
|
2056
|
-
|
|
2057
|
-
|
|
2058
|
-
|
|
2059
|
-
|
|
2060
|
-
|
|
2061
|
-
|
|
2062
|
-
|
|
2063
|
-
|
|
2064
|
-
|
|
2065
|
-
|
|
2066
|
-
|
|
2067
|
-
|
|
2068
|
-
|
|
2069
|
-
|
|
2070
|
-
|
|
2310
|
+
// Check for createClient imports even without immediate query usage
|
|
2311
|
+
// This catches cases where createClient is imported but may be used later
|
|
2312
|
+
if (hasCreateClientImport && !usesSecureSupabase) {
|
|
2313
|
+
const isConfigFile = /config|supabase|client/i.test(relativePath) &&
|
|
2314
|
+
(relativePath.includes('supabase.ts') ||
|
|
2315
|
+
relativePath.includes('supabase.js') ||
|
|
2316
|
+
relativePath.includes('client.ts') ||
|
|
2317
|
+
relativePath.includes('client.js'));
|
|
2318
|
+
|
|
2319
|
+
if (!isConfigFile) {
|
|
2320
|
+
// Find the line number of the import
|
|
2321
|
+
const importMatch = content.match(/import\s+{\s*createClient\s*}\s+from\s+['"]@supabase\/supabase-js['"]/);
|
|
2322
|
+
if (importMatch) {
|
|
2323
|
+
const lineNumber = content.substring(0, importMatch.index).split('\n').length;
|
|
2324
|
+
|
|
2325
|
+
// Check if this violation is already reported
|
|
2326
|
+
const alreadyReported = violations.directSupabaseClient.some(v =>
|
|
2327
|
+
v.file === relativePath &&
|
|
2328
|
+
v.variable === 'createClient' &&
|
|
2329
|
+
Math.abs(v.line - lineNumber) <= 2
|
|
2330
|
+
);
|
|
2331
|
+
|
|
2332
|
+
if (!alreadyReported) {
|
|
2333
|
+
violations.directSupabaseClient.push({
|
|
2334
|
+
file: relativePath,
|
|
2335
|
+
line: lineNumber,
|
|
2336
|
+
variable: 'createClient',
|
|
2337
|
+
table: 'none',
|
|
2338
|
+
reason: 'Direct import of createClient from @supabase/supabase-js detected. You MUST use useSecureSupabase() from @jmruthers/pace-core/rbac instead to ensure organisation context and RLS policies are enforced.',
|
|
2339
|
+
recommendation: 'Remove this import and use: import { useSecureSupabase } from \'@jmruthers/pace-core/rbac\'; const supabase = useSecureSupabase();'
|
|
2340
|
+
});
|
|
2341
|
+
}
|
|
2342
|
+
}
|
|
2071
2343
|
}
|
|
2072
2344
|
}
|
|
2073
2345
|
|
|
2074
|
-
// Check
|
|
2075
|
-
//
|
|
2076
|
-
|
|
2077
|
-
|
|
2078
|
-
|
|
2079
|
-
|
|
2080
|
-
|
|
2081
|
-
|
|
2082
|
-
|
|
2083
|
-
|
|
2084
|
-
|
|
2085
|
-
|
|
2086
|
-
|
|
2087
|
-
|
|
2088
|
-
|
|
2089
|
-
|
|
2090
|
-
|
|
2091
|
-
|
|
2092
|
-
|
|
2093
|
-
|
|
2094
|
-
|
|
2095
|
-
|
|
2096
|
-
|
|
2097
|
-
|
|
2098
|
-
|
|
2099
|
-
|
|
2100
|
-
|
|
2101
|
-
|
|
2102
|
-
|
|
2103
|
-
|
|
2104
|
-
|
|
2105
|
-
|
|
2106
|
-
// Check if hooks are only auth-related
|
|
2107
|
-
const authHookPattern = /use(UnifiedAuth|Permissions|Can|RBAC)/;
|
|
2108
|
-
const allHooks = componentBody.match(/use[A-Z]\w+/g) || [];
|
|
2109
|
-
const onlyAuthHooks = allHooks.length === 0 || allHooks.every(hook => authHookPattern.test(hook));
|
|
2110
|
-
|
|
2111
|
-
const isSimpleWrapper =
|
|
2112
|
-
wrappedComponentCount <= 2 && // Opening and closing tag, or self-closing
|
|
2113
|
-
!hasState &&
|
|
2114
|
-
!hasLoops &&
|
|
2115
|
-
(hasMultipleReturns === false || (hasMultipleReturns && hasOnlyAuthLogic && conditionalReturns <= 2)) &&
|
|
2116
|
-
(!hasConditionals || hasOnlyAuthLogic) &&
|
|
2117
|
-
(!hasHooks || (onlyAuthHooks && hasOnlyAuthLogic));
|
|
2118
|
-
|
|
2119
|
-
if (isSimpleWrapper) {
|
|
2120
|
-
// Check if the wrapped component is from pace-core or local
|
|
2121
|
-
const wrappedComponentImport = allImports.find(imp => imp.name === wrappedComponent);
|
|
2122
|
-
const isPaceCoreComponent = wrappedComponentImport &&
|
|
2123
|
-
wrappedComponentImport.module === '@jmruthers/pace-core';
|
|
2124
|
-
|
|
2125
|
-
let reason, recommendation;
|
|
2126
|
-
if (isPaceCoreComponent) {
|
|
2127
|
-
reason = `Component '${componentName}' appears to be an unnecessary wrapper around pace-core's '${wrappedComponent}'. It only forwards props without adding functionality.`;
|
|
2128
|
-
recommendation = `Use '${wrappedComponent}' directly from '@jmruthers/pace-core' instead of wrapping it in '${componentName}'.`;
|
|
2129
|
-
} else if (hasOnlyAuthLogic) {
|
|
2130
|
-
reason = `Component '${componentName}' is an unnecessary wrapper around '${wrappedComponent}'. It only performs auth/permission checks and returns the wrapped component.`;
|
|
2131
|
-
recommendation = `Merge the auth/permission logic into '${wrappedComponent}' and rename it to '${componentName}', or use PagePermissionGuard from pace-core to protect the route instead.`;
|
|
2132
|
-
} else {
|
|
2133
|
-
reason = `Component '${componentName}' appears to be an unnecessary wrapper around '${wrappedComponent}'. It only forwards props without adding functionality.`;
|
|
2134
|
-
recommendation = `Remove the wrapper and use '${wrappedComponent}' directly, or merge the logic into '${wrappedComponent}' and rename it to '${componentName}'.`;
|
|
2346
|
+
// Check for createClient() calls even when variable isn't used for queries yet
|
|
2347
|
+
// This catches potential security issues early
|
|
2348
|
+
if (hasCreateClientUsage && !usesSecureSupabase) {
|
|
2349
|
+
const isConfigFile = /config|supabase|client/i.test(relativePath) &&
|
|
2350
|
+
(relativePath.includes('supabase.ts') ||
|
|
2351
|
+
relativePath.includes('supabase.js') ||
|
|
2352
|
+
relativePath.includes('client.ts') ||
|
|
2353
|
+
relativePath.includes('client.js'));
|
|
2354
|
+
|
|
2355
|
+
if (!isConfigFile) {
|
|
2356
|
+
// Find all createClient() calls
|
|
2357
|
+
const createClientCallPattern = /createClient\s*\(/g;
|
|
2358
|
+
let callMatch;
|
|
2359
|
+
while ((callMatch = createClientCallPattern.exec(content)) !== null) {
|
|
2360
|
+
const lineNumber = content.substring(0, callMatch.index).split('\n').length;
|
|
2361
|
+
|
|
2362
|
+
// Check if this violation is already reported
|
|
2363
|
+
const alreadyReported = violations.directSupabaseClient.some(v =>
|
|
2364
|
+
v.file === relativePath &&
|
|
2365
|
+
Math.abs(v.line - lineNumber) <= 2
|
|
2366
|
+
);
|
|
2367
|
+
|
|
2368
|
+
if (!alreadyReported) {
|
|
2369
|
+
violations.directSupabaseClient.push({
|
|
2370
|
+
file: relativePath,
|
|
2371
|
+
line: lineNumber,
|
|
2372
|
+
variable: 'unknown',
|
|
2373
|
+
table: 'none',
|
|
2374
|
+
reason: 'Direct createClient() call detected. You MUST use useSecureSupabase() from @jmruthers/pace-core/rbac instead to ensure organisation context and RLS policies are enforced.',
|
|
2375
|
+
recommendation: 'Replace with: import { useSecureSupabase } from \'@jmruthers/pace-core/rbac\'; const supabase = useSecureSupabase();'
|
|
2376
|
+
});
|
|
2377
|
+
}
|
|
2135
2378
|
}
|
|
2136
|
-
|
|
2137
|
-
issues.push({
|
|
2138
|
-
component: componentName,
|
|
2139
|
-
wrappedComponent: wrappedComponent,
|
|
2140
|
-
file: relativePath,
|
|
2141
|
-
line: getLineNumber(content, match[0]),
|
|
2142
|
-
reason: reason,
|
|
2143
|
-
recommendation: recommendation
|
|
2144
|
-
});
|
|
2145
2379
|
}
|
|
2146
2380
|
}
|
|
2147
|
-
});
|
|
2148
|
-
|
|
2149
|
-
return issues;
|
|
2150
|
-
}
|
|
2151
|
-
|
|
2152
|
-
// Get line number for a match
|
|
2153
|
-
function getLineNumber(content, match) {
|
|
2154
|
-
const lines = content.substring(0, content.indexOf(match)).split('\n');
|
|
2155
|
-
return lines.length;
|
|
2156
|
-
}
|
|
2157
|
-
|
|
2158
|
-
// Generate report
|
|
2159
|
-
function generateReport(allViolations, manifest) {
|
|
2160
|
-
const totalRestricted = allViolations.restrictedImports.length;
|
|
2161
|
-
const totalDuplicates =
|
|
2162
|
-
allViolations.duplicateComponents.length +
|
|
2163
|
-
allViolations.duplicateHooks.length +
|
|
2164
|
-
allViolations.duplicateUtils.length;
|
|
2165
|
-
const totalSuggestions = allViolations.suggestions.length;
|
|
2166
|
-
const totalRbacAuth =
|
|
2167
|
-
allViolations.customAuthCode.length +
|
|
2168
|
-
allViolations.duplicateConfig.length +
|
|
2169
|
-
allViolations.unprotectedPages.length +
|
|
2170
|
-
allViolations.directSupabaseAuth.length;
|
|
2171
|
-
const totalSetupIssues =
|
|
2172
|
-
allViolations.providerSetupIssues.length +
|
|
2173
|
-
allViolations.viteConfigIssues.length +
|
|
2174
|
-
allViolations.routerSetupIssues.length;
|
|
2175
|
-
const totalUnnecessaryWrappers = allViolations.unnecessaryWrappers.length;
|
|
2176
|
-
const totalAppDiscovery = allViolations.appDiscoveryIssues.length;
|
|
2177
|
-
const totalIssues = totalRestricted + totalDuplicates + totalSuggestions + totalRbacAuth + totalSetupIssues + totalUnnecessaryWrappers + totalAppDiscovery;
|
|
2178
|
-
|
|
2179
|
-
console.log(`\n${colors.bold}${colors.cyan}═══════════════════════════════════════════════════════════${colors.reset}`);
|
|
2180
|
-
console.log(`${colors.bold}${colors.cyan} pace-core Compliance Report${colors.reset}`);
|
|
2181
|
-
console.log(`${colors.bold}${colors.cyan}═══════════════════════════════════════════════════════════${colors.reset}\n`);
|
|
2182
|
-
|
|
2183
|
-
// Restricted Imports
|
|
2184
|
-
if (totalRestricted > 0) {
|
|
2185
|
-
console.log(`${colors.red}${colors.bold}❌ Restricted Imports Found: ${totalRestricted}${colors.reset}\n`);
|
|
2186
|
-
allViolations.restrictedImports.forEach(({ module, reason, file, line }) => {
|
|
2187
|
-
console.log(` ${colors.red}•${colors.reset} ${colors.yellow}${file}:${line}${colors.reset}`);
|
|
2188
|
-
console.log(` Import: ${colors.cyan}${module}${colors.reset}`);
|
|
2189
|
-
console.log(` ${colors.yellow}Reason:${colors.reset} ${reason}\n`);
|
|
2190
|
-
});
|
|
2191
|
-
} else {
|
|
2192
|
-
console.log(`${colors.green}✅ No restricted imports found${colors.reset}\n`);
|
|
2193
|
-
}
|
|
2194
|
-
|
|
2195
|
-
// Duplicate Components
|
|
2196
|
-
if (allViolations.duplicateComponents.length > 0) {
|
|
2197
|
-
console.log(`${colors.red}${colors.bold}❌ Duplicate Components Found: ${allViolations.duplicateComponents.length}${colors.reset}\n`);
|
|
2198
|
-
allViolations.duplicateComponents.forEach(({ component, file }) => {
|
|
2199
|
-
console.log(` ${colors.red}•${colors.reset} ${colors.yellow}${file}${colors.reset}`);
|
|
2200
|
-
console.log(` Component '${colors.cyan}${component}${colors.reset}' conflicts with pace-core component`);
|
|
2201
|
-
console.log(` ${colors.yellow}Suggestion:${colors.reset} Use '${component}' from '@jmruthers/pace-core' instead\n`);
|
|
2202
|
-
});
|
|
2203
|
-
}
|
|
2204
|
-
|
|
2205
|
-
// Duplicate Hooks
|
|
2206
|
-
if (allViolations.duplicateHooks.length > 0) {
|
|
2207
|
-
console.log(`${colors.red}${colors.bold}❌ Duplicate Hooks Found: ${allViolations.duplicateHooks.length}${colors.reset}\n`);
|
|
2208
|
-
allViolations.duplicateHooks.forEach(({ hook, file }) => {
|
|
2209
|
-
console.log(` ${colors.red}•${colors.reset} ${colors.yellow}${file}${colors.reset}`);
|
|
2210
|
-
console.log(` Hook '${colors.cyan}${hook}${colors.reset}' conflicts with pace-core hook`);
|
|
2211
|
-
console.log(` ${colors.yellow}Suggestion:${colors.reset} Use '${hook}' from '@jmruthers/pace-core' instead\n`);
|
|
2212
|
-
});
|
|
2213
|
-
}
|
|
2214
|
-
|
|
2215
|
-
// Duplicate Utils
|
|
2216
|
-
if (allViolations.duplicateUtils.length > 0) {
|
|
2217
|
-
console.log(`${colors.red}${colors.bold}❌ Duplicate Utils Found: ${allViolations.duplicateUtils.length}${colors.reset}\n`);
|
|
2218
|
-
allViolations.duplicateUtils.forEach(({ util, file }) => {
|
|
2219
|
-
console.log(` ${colors.red}•${colors.reset} ${colors.yellow}${file}${colors.reset}`);
|
|
2220
|
-
console.log(` Util '${colors.cyan}${util}${colors.reset}' conflicts with pace-core util`);
|
|
2221
|
-
console.log(` ${colors.yellow}Suggestion:${colors.reset} Use '${util}' from '@jmruthers/pace-core' instead\n`);
|
|
2222
|
-
});
|
|
2223
|
-
}
|
|
2224
|
-
|
|
2225
|
-
// Suggestions
|
|
2226
|
-
if (totalSuggestions > 0) {
|
|
2227
|
-
console.log(`${colors.yellow}${colors.bold}💡 Suggestions: ${totalSuggestions}${colors.reset}\n`);
|
|
2228
|
-
const grouped = {};
|
|
2229
|
-
allViolations.suggestions.forEach(s => {
|
|
2230
|
-
if (!grouped[s.file]) grouped[s.file] = [];
|
|
2231
|
-
grouped[s.file].push(s);
|
|
2232
|
-
});
|
|
2233
|
-
Object.entries(grouped).forEach(([file, suggestions]) => {
|
|
2234
|
-
console.log(` ${colors.yellow}•${colors.reset} ${colors.yellow}${file}${colors.reset}`);
|
|
2235
|
-
suggestions.forEach(s => {
|
|
2236
|
-
console.log(` ${s.suggestion}\n`);
|
|
2237
|
-
});
|
|
2238
|
-
});
|
|
2239
2381
|
}
|
|
2240
2382
|
|
|
2241
|
-
//
|
|
2242
|
-
|
|
2243
|
-
|
|
2244
|
-
|
|
2245
|
-
|
|
2246
|
-
|
|
2247
|
-
console.log(` Wraps: ${colors.cyan}${wrappedComponent}${colors.reset}`);
|
|
2248
|
-
console.log(` ${colors.yellow}Issue:${colors.reset} ${reason}`);
|
|
2249
|
-
if (recommendation) {
|
|
2250
|
-
console.log(` ${colors.green}Recommendation:${colors.reset} ${recommendation}\n`);
|
|
2251
|
-
} else {
|
|
2252
|
-
console.log(` ${colors.green}Recommendation:${colors.reset} Remove the wrapper and use the component directly.\n`);
|
|
2253
|
-
}
|
|
2254
|
-
});
|
|
2255
|
-
}
|
|
2383
|
+
// ============================================
|
|
2384
|
+
// Check for Deprecated useSecureDataAccess Usage
|
|
2385
|
+
// ============================================
|
|
2386
|
+
// Detect when consuming apps use useSecureDataAccess() with secureQuery/secureInsert/etc
|
|
2387
|
+
// This is deprecated - they should migrate to useSecureSupabase() instead
|
|
2388
|
+
// This helps identify code that needs migration before retiring the old API
|
|
2256
2389
|
|
|
2257
|
-
//
|
|
2258
|
-
if (
|
|
2259
|
-
|
|
2260
|
-
|
|
2261
|
-
|
|
2262
|
-
|
|
2263
|
-
// Separate by type
|
|
2264
|
-
const directQueries = allViolations.appDiscoveryIssues.filter(v => v.type === 'direct_table_query');
|
|
2265
|
-
const hardcodedNames = allViolations.appDiscoveryIssues.filter(v => v.type === 'hardcoded_app_name');
|
|
2266
|
-
const suggestions = allViolations.appDiscoveryIssues.filter(v => v.type === 'suggestion');
|
|
2390
|
+
// Skip Edge Functions - they run in Deno
|
|
2391
|
+
if (!isEdgeFunction) {
|
|
2392
|
+
// Check for useSecureDataAccess import
|
|
2393
|
+
const hasSecureDataAccessImport = /import.*useSecureDataAccess.*from\s+['"]@jmruthers\/pace-core['"]/.test(content) ||
|
|
2394
|
+
/import.*useSecureDataAccess.*from\s+['"]@jmruthers\/pace-core\/hooks['"]/.test(content);
|
|
2267
2395
|
|
|
2268
|
-
|
|
2269
|
-
|
|
2270
|
-
directQueries.forEach(({ file, line, reason, recommendation }) => {
|
|
2271
|
-
console.log(` ${colors.yellow}•${colors.reset} ${colors.yellow}${file}:${line}${colors.reset}`);
|
|
2272
|
-
console.log(` ${colors.yellow}Issue:${colors.reset} ${reason}`);
|
|
2273
|
-
console.log(` ${colors.green}Fix:${colors.reset} ${recommendation}\n`);
|
|
2274
|
-
});
|
|
2275
|
-
}
|
|
2396
|
+
// Check for useSecureDataAccess hook usage
|
|
2397
|
+
const hasSecureDataAccessHook = /useSecureDataAccess\s*\(/.test(content);
|
|
2276
2398
|
|
|
2277
|
-
|
|
2278
|
-
|
|
2279
|
-
|
|
2280
|
-
|
|
2281
|
-
|
|
2282
|
-
|
|
2283
|
-
|
|
2284
|
-
|
|
2285
|
-
}
|
|
2399
|
+
// Check for deprecated secure methods
|
|
2400
|
+
const deprecatedMethods = [
|
|
2401
|
+
{ name: 'secureQuery', operation: 'query' },
|
|
2402
|
+
{ name: 'secureInsert', operation: 'insert' },
|
|
2403
|
+
{ name: 'secureUpdate', operation: 'update' },
|
|
2404
|
+
{ name: 'secureDelete', operation: 'delete' },
|
|
2405
|
+
{ name: 'secureRpc', operation: 'RPC call' }
|
|
2406
|
+
];
|
|
2286
2407
|
|
|
2287
|
-
|
|
2288
|
-
|
|
2289
|
-
suggestions.forEach(({ file, reason, recommendation }) => {
|
|
2290
|
-
console.log(` ${colors.cyan}•${colors.reset} ${colors.yellow}${file}${colors.reset}`);
|
|
2291
|
-
console.log(` ${reason}`);
|
|
2292
|
-
console.log(` ${colors.green}Recommendation:${colors.reset} ${recommendation}\n`);
|
|
2293
|
-
});
|
|
2294
|
-
}
|
|
2408
|
+
// Pattern to find destructured secure methods from useSecureDataAccess
|
|
2409
|
+
const destructurePattern = /(const|let)\s*\{[^}]*\b(secureQuery|secureInsert|secureUpdate|secureDelete|secureRpc)\b[^}]*\}\s*=\s*useSecureDataAccess\s*\(/g;
|
|
2295
2410
|
|
|
2296
|
-
|
|
2297
|
-
|
|
2298
|
-
|
|
2299
|
-
|
|
2300
|
-
|
|
2301
|
-
|
|
2302
|
-
|
|
2303
|
-
// RBAC/Auth Compliance Section
|
|
2304
|
-
if (totalRbacAuth > 0) {
|
|
2305
|
-
console.log(`\n${colors.bold}${colors.cyan}═══════════════════════════════════════════════════════════${colors.reset}`);
|
|
2306
|
-
console.log(`${colors.bold}${colors.cyan} RBAC/Auth Compliance${colors.reset}`);
|
|
2307
|
-
console.log(`${colors.bold}${colors.cyan}═══════════════════════════════════════════════════════════${colors.reset}\n`);
|
|
2411
|
+
// Pattern to find direct method calls (secureQuery(...), secureInsert(...), etc.)
|
|
2412
|
+
const methodCallPatterns = deprecatedMethods.map(method => ({
|
|
2413
|
+
name: method.name,
|
|
2414
|
+
operation: method.operation,
|
|
2415
|
+
pattern: new RegExp(`\\b${method.name}\\s*\\(`, 'g')
|
|
2416
|
+
}));
|
|
2308
2417
|
|
|
2309
|
-
//
|
|
2310
|
-
if (
|
|
2311
|
-
//
|
|
2312
|
-
|
|
2313
|
-
const
|
|
2314
|
-
|
|
2315
|
-
|
|
2316
|
-
|
|
2317
|
-
|
|
2318
|
-
|
|
2319
|
-
|
|
2320
|
-
console.log(` ${type}: ${colors.cyan}${name}${colors.reset}`);
|
|
2321
|
-
console.log(` ${colors.yellow}Reason:${colors.reset} ${reason}`);
|
|
2322
|
-
if (replacement) {
|
|
2323
|
-
console.log(` ${colors.green}Fix:${colors.reset} ${replacement}`);
|
|
2324
|
-
// Add example code if provided
|
|
2325
|
-
if (example) {
|
|
2326
|
-
console.log(` ${colors.cyan}Example:${colors.reset}`);
|
|
2327
|
-
example.split('\n').forEach(line => {
|
|
2328
|
-
console.log(` ${colors.green}${line}${colors.reset}`);
|
|
2329
|
-
});
|
|
2330
|
-
} else {
|
|
2331
|
-
// Add example code for common cases
|
|
2332
|
-
if (type === 'rbac query' && name.includes('rbac_user_profiles')) {
|
|
2333
|
-
console.log(` ${colors.cyan}Example:${colors.reset}`);
|
|
2334
|
-
console.log(` ${colors.green}import { useSecureSupabase } from '@jmruthers/pace-core/rbac';${colors.reset}`);
|
|
2335
|
-
console.log(` ${colors.green}const supabase = useSecureSupabase();${colors.reset}`);
|
|
2336
|
-
console.log(` ${colors.green}const { data } = await supabase.from('rbac_user_profiles').select('*');${colors.reset}`);
|
|
2337
|
-
} else if (type === 'rbac query' && name.includes('rbac_user_login_history')) {
|
|
2338
|
-
console.log(` ${colors.cyan}Example:${colors.reset}`);
|
|
2339
|
-
console.log(` ${colors.green}import { useSecureSupabase } from '@jmruthers/pace-core/rbac';${colors.reset}`);
|
|
2340
|
-
console.log(` ${colors.green}const supabase = useSecureSupabase();${colors.reset}`);
|
|
2341
|
-
console.log(` ${colors.green}const { data } = await supabase.from('rbac_user_login_history').select('*').eq('user_id', userId);${colors.reset}`);
|
|
2342
|
-
console.log(` ${colors.yellow}Note:${colors.reset} Login history is automatically tracked by UnifiedAuthProvider.`);
|
|
2343
|
-
console.log(` ${colors.yellow} ${colors.reset} Queries should use useSecureSupabase to ensure organisation context is enforced.`);
|
|
2344
|
-
} else if (type === 'rbac query' && name.includes('rbac_user_units')) {
|
|
2345
|
-
console.log(` ${colors.cyan}Example:${colors.reset}`);
|
|
2346
|
-
console.log(` ${colors.green}import { useSecureSupabase } from '@jmruthers/pace-core/rbac';${colors.reset}`);
|
|
2347
|
-
console.log(` ${colors.green}const supabase = useSecureSupabase();${colors.reset}`);
|
|
2348
|
-
console.log(` ${colors.green}// For reading, use RPC:${colors.reset}`);
|
|
2349
|
-
console.log(` ${colors.green}const { data } = await supabase.rpc('data_user_unit_get', {${colors.reset}`);
|
|
2350
|
-
console.log(` ${colors.green} p_user_id: userId,${colors.reset}`);
|
|
2351
|
-
console.log(` ${colors.green} p_event_id: eventId${colors.reset}`);
|
|
2352
|
-
console.log(` ${colors.green}});${colors.reset}`);
|
|
2353
|
-
} else if (type === 'rbac query' && name.includes('rbac_apps')) {
|
|
2354
|
-
console.log(` ${colors.cyan}Example (app discovery):${colors.reset}`);
|
|
2355
|
-
console.log(` ${colors.green}const { data: apps } = await supabase.rpc('data_rbac_apps_list');${colors.reset}`);
|
|
2356
|
-
console.log(` ${colors.yellow}Note:${colors.reset} Use data_rbac_apps_list RPC function for dynamic app discovery instead of querying rbac_apps directly.`);
|
|
2357
|
-
} else if (type === 'rbac query' && (name.includes('rbac_app_pages') || name.includes('rbac_page_permissions'))) {
|
|
2358
|
-
console.log(` ${colors.cyan}Example (admin operations):${colors.reset}`);
|
|
2359
|
-
console.log(` ${colors.green}import { useSecureSupabase } from '@jmruthers/pace-core/rbac';${colors.reset}`);
|
|
2360
|
-
console.log(` ${colors.green}const supabase = useSecureSupabase();${colors.reset}`);
|
|
2361
|
-
console.log(` ${colors.green}const { data } = await supabase.from('${name.match(/rbac_\w+/)?.[0] || 'rbac_table'}').select('*');${colors.reset}`);
|
|
2362
|
-
}
|
|
2363
|
-
}
|
|
2418
|
+
// Check if file uses the deprecated hook
|
|
2419
|
+
if (hasSecureDataAccessImport || hasSecureDataAccessHook) {
|
|
2420
|
+
// Find all destructured methods
|
|
2421
|
+
let destructureMatch;
|
|
2422
|
+
const foundMethods = new Set();
|
|
2423
|
+
|
|
2424
|
+
while ((destructureMatch = destructurePattern.exec(content)) !== null) {
|
|
2425
|
+
const destructureText = destructureMatch[0];
|
|
2426
|
+
deprecatedMethods.forEach(method => {
|
|
2427
|
+
if (destructureText.includes(method.name)) {
|
|
2428
|
+
foundMethods.add(method.name);
|
|
2364
2429
|
}
|
|
2365
|
-
console.log('');
|
|
2366
2430
|
});
|
|
2367
2431
|
}
|
|
2368
2432
|
|
|
2369
|
-
|
|
2370
|
-
|
|
2371
|
-
|
|
2372
|
-
|
|
2373
|
-
|
|
2374
|
-
|
|
2375
|
-
|
|
2376
|
-
|
|
2433
|
+
// Find all method calls
|
|
2434
|
+
methodCallPatterns.forEach(({ name, operation, pattern }) => {
|
|
2435
|
+
let match;
|
|
2436
|
+
pattern.lastIndex = 0;
|
|
2437
|
+
|
|
2438
|
+
while ((match = pattern.exec(content)) !== null) {
|
|
2439
|
+
const matchIndex = match.index;
|
|
2440
|
+
|
|
2441
|
+
// Skip if in a line comment
|
|
2442
|
+
const lineStart = content.lastIndexOf('\n', matchIndex) + 1;
|
|
2443
|
+
const lineUpToMatch = content.substring(lineStart, matchIndex);
|
|
2444
|
+
const isInLineComment = /\/\/[^\n]*$/.test(lineUpToMatch);
|
|
2445
|
+
|
|
2446
|
+
if (isInLineComment) {
|
|
2447
|
+
continue;
|
|
2377
2448
|
}
|
|
2378
|
-
|
|
2379
|
-
|
|
2380
|
-
|
|
2381
|
-
|
|
2382
|
-
|
|
2449
|
+
|
|
2450
|
+
// Check if this is from useSecureDataAccess (not from a different source)
|
|
2451
|
+
// Look backwards to see if it's from useSecureDataAccess destructuring
|
|
2452
|
+
const beforeMatch = content.substring(Math.max(0, matchIndex - 500), matchIndex);
|
|
2453
|
+
const isFromSecureDataAccess =
|
|
2454
|
+
/useSecureDataAccess\s*\(/.test(beforeMatch) ||
|
|
2455
|
+
/(const|let)\s*\{[^}]*\bsecure(Query|Insert|Update|Delete|Rpc)\b/.test(beforeMatch);
|
|
2456
|
+
|
|
2457
|
+
if (isFromSecureDataAccess) {
|
|
2458
|
+
foundMethods.add(name);
|
|
2459
|
+
|
|
2460
|
+
const lineNumber = content.substring(0, matchIndex).split('\n').length;
|
|
2461
|
+
|
|
2462
|
+
// Check if already reported
|
|
2463
|
+
const alreadyReported = violations.deprecatedSecureDataAccess.some(v =>
|
|
2464
|
+
v.file === relativePath &&
|
|
2465
|
+
v.method === name &&
|
|
2466
|
+
Math.abs(v.line - lineNumber) <= 2
|
|
2467
|
+
);
|
|
2468
|
+
|
|
2469
|
+
if (!alreadyReported) {
|
|
2470
|
+
violations.deprecatedSecureDataAccess.push({
|
|
2471
|
+
file: relativePath,
|
|
2472
|
+
line: lineNumber,
|
|
2473
|
+
method: name,
|
|
2474
|
+
operation: operation,
|
|
2475
|
+
reason: `Deprecated method '${name}' from useSecureDataAccess() detected. This API is being retired. Migrate to useSecureSupabase() instead.`,
|
|
2476
|
+
recommendation: getMigrationRecommendation(name, operation)
|
|
2477
|
+
});
|
|
2478
|
+
}
|
|
2383
2479
|
}
|
|
2384
|
-
console.log('');
|
|
2385
|
-
});
|
|
2386
|
-
}
|
|
2387
|
-
|
|
2388
|
-
// Don't show info-level items (these are correct usage)
|
|
2389
|
-
// They're tracked but not displayed to avoid noise
|
|
2390
|
-
}
|
|
2391
|
-
|
|
2392
|
-
// Duplicate Configurations
|
|
2393
|
-
if (allViolations.duplicateConfig.length > 0) {
|
|
2394
|
-
console.log(`${colors.red}${colors.bold}❌ Duplicate Configurations Found: ${allViolations.duplicateConfig.length}${colors.reset}\n`);
|
|
2395
|
-
allViolations.duplicateConfig.forEach(({ type, file, count, reason }) => {
|
|
2396
|
-
console.log(` ${colors.red}•${colors.reset} ${colors.yellow}${file}${colors.reset}`);
|
|
2397
|
-
console.log(` Type: ${colors.cyan}${type}${colors.reset}${count ? ` (${count} instances)` : ''}`);
|
|
2398
|
-
console.log(` ${colors.yellow}Reason:${colors.reset} ${reason}\n`);
|
|
2399
|
-
});
|
|
2400
|
-
}
|
|
2401
|
-
|
|
2402
|
-
// Unprotected Pages
|
|
2403
|
-
if (allViolations.unprotectedPages.length > 0) {
|
|
2404
|
-
console.log(`${colors.red}${colors.bold}❌ Unprotected Pages Found: ${allViolations.unprotectedPages.length}${colors.reset}\n`);
|
|
2405
|
-
allViolations.unprotectedPages.forEach(({ file, reason }) => {
|
|
2406
|
-
console.log(` ${colors.red}•${colors.reset} ${colors.yellow}${file}${colors.reset}`);
|
|
2407
|
-
console.log(` ${colors.yellow}Reason:${colors.reset} ${reason}\n`);
|
|
2408
|
-
});
|
|
2409
|
-
}
|
|
2410
|
-
|
|
2411
|
-
// Direct Supabase Auth Usage
|
|
2412
|
-
if (allViolations.directSupabaseAuth.length > 0) {
|
|
2413
|
-
console.log(`${colors.red}${colors.bold}❌ Direct Supabase Auth Usage Found: ${allViolations.directSupabaseAuth.length}${colors.reset}\n`);
|
|
2414
|
-
allViolations.directSupabaseAuth.forEach(({ file, line, reason, method, recommendation }) => {
|
|
2415
|
-
console.log(` ${colors.red}•${colors.reset} ${colors.yellow}${file}:${line}${colors.reset}`);
|
|
2416
|
-
console.log(` ${colors.yellow}Reason:${colors.reset} ${reason}`);
|
|
2417
|
-
if (recommendation) {
|
|
2418
|
-
console.log(` ${colors.green}Fix:${colors.reset} ${recommendation}\n`);
|
|
2419
|
-
} else {
|
|
2420
|
-
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`);
|
|
2421
2480
|
}
|
|
2422
2481
|
});
|
|
2423
|
-
|
|
2424
|
-
|
|
2425
|
-
|
|
2426
|
-
|
|
2427
|
-
|
|
2428
|
-
|
|
2429
|
-
|
|
2430
|
-
|
|
2431
|
-
|
|
2432
|
-
|
|
2433
|
-
|
|
2434
|
-
|
|
2435
|
-
|
|
2436
|
-
|
|
2437
|
-
|
|
2438
|
-
console.log(` ${colors.red}•${colors.reset} ${colors.yellow}${file}:${line}${colors.reset}`);
|
|
2439
|
-
if (provider) {
|
|
2440
|
-
console.log(` Missing Provider: ${colors.cyan}${provider}${colors.reset}`);
|
|
2441
|
-
}
|
|
2442
|
-
if (type) {
|
|
2443
|
-
console.log(` Issue Type: ${colors.cyan}${type}${colors.reset}`);
|
|
2482
|
+
|
|
2483
|
+
// If we found the hook usage but haven't reported specific methods, add a general warning
|
|
2484
|
+
if (foundMethods.size === 0 && hasSecureDataAccessHook) {
|
|
2485
|
+
// Check if it's just imported but not used, or used in a way we didn't detect
|
|
2486
|
+
const hasAnySecureMethodCall = /secure(Query|Insert|Update|Delete|Rpc)\s*\(/.test(content);
|
|
2487
|
+
|
|
2488
|
+
if (hasAnySecureMethodCall) {
|
|
2489
|
+
violations.deprecatedSecureDataAccess.push({
|
|
2490
|
+
file: relativePath,
|
|
2491
|
+
line: 1,
|
|
2492
|
+
method: 'useSecureDataAccess',
|
|
2493
|
+
operation: 'general',
|
|
2494
|
+
reason: 'useSecureDataAccess() hook detected. This API is deprecated and will be retired. Migrate to useSecureSupabase() instead.',
|
|
2495
|
+
recommendation: 'Replace useSecureDataAccess() with useSecureSupabase() and use standard Supabase query builder API (.from(), .select(), etc.)'
|
|
2496
|
+
});
|
|
2444
2497
|
}
|
|
2445
|
-
|
|
2446
|
-
console.log(` ${colors.green}Fix:${colors.reset} ${recommendation}\n`);
|
|
2447
|
-
});
|
|
2448
|
-
}
|
|
2449
|
-
|
|
2450
|
-
// Vite Configuration Issues
|
|
2451
|
-
if (allViolations.viteConfigIssues.length > 0) {
|
|
2452
|
-
console.log(`${colors.red}${colors.bold}❌ Vite Configuration Issues Found: ${allViolations.viteConfigIssues.length}${colors.reset}\n`);
|
|
2453
|
-
allViolations.viteConfigIssues.forEach(({ file, line, type, reason, recommendation }) => {
|
|
2454
|
-
const severity = type === 'recommendation' ? colors.yellow : colors.red;
|
|
2455
|
-
const icon = type === 'recommendation' ? '💡' : '❌';
|
|
2456
|
-
console.log(` ${severity}${icon}${colors.reset} ${colors.yellow}${file}:${line}${colors.reset}`);
|
|
2457
|
-
console.log(` ${colors.yellow}Issue:${colors.reset} ${reason}`);
|
|
2458
|
-
console.log(` ${colors.green}Fix:${colors.reset} ${recommendation}\n`);
|
|
2459
|
-
});
|
|
2460
|
-
}
|
|
2461
|
-
|
|
2462
|
-
// Router Setup Issues
|
|
2463
|
-
if (allViolations.routerSetupIssues.length > 0) {
|
|
2464
|
-
console.log(`${colors.red}${colors.bold}❌ Router Setup Issues Found: ${allViolations.routerSetupIssues.length}${colors.reset}\n`);
|
|
2465
|
-
allViolations.routerSetupIssues.forEach(({ file, line, type, reason, recommendation }) => {
|
|
2466
|
-
console.log(` ${colors.red}•${colors.reset} ${colors.yellow}${file}:${line}${colors.reset}`);
|
|
2467
|
-
console.log(` Issue Type: ${colors.cyan}${type}${colors.reset}`);
|
|
2468
|
-
console.log(` ${colors.yellow}Problem:${colors.reset} ${reason}`);
|
|
2469
|
-
console.log(` ${colors.green}Fix:${colors.reset} ${recommendation}\n`);
|
|
2470
|
-
});
|
|
2498
|
+
}
|
|
2471
2499
|
}
|
|
2472
|
-
} else {
|
|
2473
|
-
console.log(`\n${colors.green}✅ Setup & Configuration: All checks passed${colors.reset}\n`);
|
|
2474
2500
|
}
|
|
2475
2501
|
|
|
2476
|
-
|
|
2477
|
-
console.log(`${colors.bold}${colors.cyan}═══════════════════════════════════════════════════════════${colors.reset}`);
|
|
2478
|
-
console.log(`${colors.bold}Summary:${colors.reset}`);
|
|
2479
|
-
console.log(` Total Issues: ${totalIssues > 0 ? colors.red : colors.green}${totalIssues}${colors.reset}`);
|
|
2480
|
-
console.log(` - Restricted Imports: ${totalRestricted > 0 ? colors.red : colors.green}${totalRestricted}${colors.reset}`);
|
|
2481
|
-
console.log(` - Duplicate Components/Hooks/Utils: ${totalDuplicates > 0 ? colors.red : colors.green}${totalDuplicates}${colors.reset}`);
|
|
2482
|
-
console.log(` - Suggestions: ${colors.yellow}${totalSuggestions}${colors.reset}`);
|
|
2483
|
-
console.log(` - RBAC/Auth Issues: ${totalRbacAuth > 0 ? colors.red : colors.green}${totalRbacAuth}${colors.reset}`);
|
|
2484
|
-
console.log(` - Setup/Configuration Issues: ${totalSetupIssues > 0 ? colors.red : colors.green}${totalSetupIssues}${colors.reset}`);
|
|
2485
|
-
console.log(` - Unnecessary Wrappers: ${totalUnnecessaryWrappers > 0 ? colors.yellow : colors.green}${totalUnnecessaryWrappers}${colors.reset}`);
|
|
2486
|
-
console.log(` - App Discovery Issues: ${totalAppDiscovery > 0 ? colors.yellow : colors.green}${totalAppDiscovery}${colors.reset}`);
|
|
2487
|
-
|
|
2488
|
-
if (totalIssues === 0) {
|
|
2489
|
-
console.log(`\n${colors.green}${colors.bold}✅ Excellent! Your codebase is fully compliant with pace-core standards.${colors.reset}\n`);
|
|
2490
|
-
return 0;
|
|
2491
|
-
} else {
|
|
2492
|
-
console.log(`\n${colors.yellow}${colors.bold}⚠️ Please review the issues above and migrate to pace-core components/hooks/utils.${colors.reset}\n`);
|
|
2493
|
-
return 1;
|
|
2494
|
-
}
|
|
2502
|
+
return violations;
|
|
2495
2503
|
}
|
|
2496
2504
|
|
|
2497
|
-
|
|
2498
|
-
|
|
2499
|
-
|
|
2500
|
-
|
|
2501
|
-
|
|
2502
|
-
|
|
2503
|
-
|
|
2504
|
-
|
|
2505
|
+
/**
|
|
2506
|
+
* Compliance check module
|
|
2507
|
+
*/
|
|
2508
|
+
const complianceCheck = {
|
|
2509
|
+
name: 'compliance',
|
|
2510
|
+
description: 'pace-core compliance checks (restricted imports, duplicates, auth/RBAC, etc.)',
|
|
2511
|
+
severity: 'error',
|
|
2512
|
+
|
|
2513
|
+
async run(context) {
|
|
2514
|
+
const { projectRoot, files, manifest: providedManifest } = context;
|
|
2505
2515
|
|
|
2506
|
-
|
|
2507
|
-
|
|
2508
|
-
|
|
2509
|
-
|
|
2510
|
-
|
|
2511
|
-
|
|
2512
|
-
|
|
2513
|
-
|
|
2514
|
-
|
|
2515
|
-
|
|
2516
|
-
|
|
2517
|
-
|
|
2518
|
-
|
|
2519
|
-
|
|
2520
|
-
|
|
2516
|
+
// Load manifest if not provided
|
|
2517
|
+
const manifest = providedManifest || loadManifest();
|
|
2518
|
+
|
|
2519
|
+
if (!files || files.length === 0) {
|
|
2520
|
+
return {
|
|
2521
|
+
issues: [],
|
|
2522
|
+
warnings: [],
|
|
2523
|
+
suggestions: [],
|
|
2524
|
+
violations: {
|
|
2525
|
+
restrictedImports: [],
|
|
2526
|
+
duplicateComponents: [],
|
|
2527
|
+
duplicateHooks: [],
|
|
2528
|
+
duplicateUtils: [],
|
|
2529
|
+
suggestions: [],
|
|
2530
|
+
customAuthCode: [],
|
|
2531
|
+
duplicateConfig: [],
|
|
2532
|
+
unprotectedPages: [],
|
|
2533
|
+
directSupabaseAuth: [],
|
|
2534
|
+
directSupabaseClient: [],
|
|
2535
|
+
deprecatedSecureDataAccess: [],
|
|
2536
|
+
providerSetupIssues: [],
|
|
2537
|
+
viteConfigIssues: [],
|
|
2538
|
+
routerSetupIssues: [],
|
|
2539
|
+
unnecessaryWrappers: [],
|
|
2540
|
+
appDiscoveryIssues: []
|
|
2521
2541
|
}
|
|
2542
|
+
};
|
|
2543
|
+
}
|
|
2544
|
+
|
|
2545
|
+
// Aggregate all violations
|
|
2546
|
+
const allViolations = {
|
|
2547
|
+
restrictedImports: [],
|
|
2548
|
+
duplicateComponents: [],
|
|
2549
|
+
duplicateHooks: [],
|
|
2550
|
+
duplicateUtils: [],
|
|
2551
|
+
suggestions: [],
|
|
2552
|
+
customAuthCode: [],
|
|
2553
|
+
duplicateConfig: [],
|
|
2554
|
+
unprotectedPages: [],
|
|
2555
|
+
directSupabaseAuth: [],
|
|
2556
|
+
directSupabaseClient: [],
|
|
2557
|
+
deprecatedSecureDataAccess: [],
|
|
2558
|
+
providerSetupIssues: [],
|
|
2559
|
+
viteConfigIssues: [],
|
|
2560
|
+
routerSetupIssues: [],
|
|
2561
|
+
unnecessaryWrappers: [],
|
|
2562
|
+
appDiscoveryIssues: []
|
|
2563
|
+
};
|
|
2564
|
+
|
|
2565
|
+
// Scan all files
|
|
2566
|
+
for (const filePath of files) {
|
|
2567
|
+
try {
|
|
2568
|
+
const violations = scanFile(filePath, manifest, projectRoot);
|
|
2569
|
+
|
|
2570
|
+
// Aggregate violations
|
|
2571
|
+
Object.keys(allViolations).forEach(key => {
|
|
2572
|
+
if (violations[key] && Array.isArray(violations[key])) {
|
|
2573
|
+
allViolations[key].push(...violations[key]);
|
|
2574
|
+
}
|
|
2575
|
+
});
|
|
2576
|
+
} catch (error) {
|
|
2577
|
+
// Skip files with errors
|
|
2578
|
+
console.warn(`Error scanning ${filePath}: ${error.message}`);
|
|
2522
2579
|
}
|
|
2523
|
-
});
|
|
2524
|
-
} catch (error) {
|
|
2525
|
-
// Skip directories we can't read
|
|
2526
|
-
}
|
|
2527
|
-
|
|
2528
|
-
return fileList;
|
|
2529
|
-
}
|
|
2530
|
-
|
|
2531
|
-
// Main function
|
|
2532
|
-
function main() {
|
|
2533
|
-
const manifest = loadManifest();
|
|
2534
|
-
const projectRoot = findProjectRoot();
|
|
2535
|
-
|
|
2536
|
-
console.log(`${colors.cyan}Scanning project at: ${projectRoot}${colors.reset}`);
|
|
2537
|
-
|
|
2538
|
-
// Find all TypeScript/JavaScript files (excluding node_modules, dist, etc.)
|
|
2539
|
-
const files = findSourceFiles(projectRoot);
|
|
2540
|
-
|
|
2541
|
-
console.log(`Found ${files.length} files to scan...\n`);
|
|
2542
|
-
|
|
2543
|
-
// Scan all files
|
|
2544
|
-
const allViolations = {
|
|
2545
|
-
restrictedImports: [],
|
|
2546
|
-
duplicateComponents: [],
|
|
2547
|
-
duplicateHooks: [],
|
|
2548
|
-
duplicateUtils: [],
|
|
2549
|
-
suggestions: [],
|
|
2550
|
-
customAuthCode: [],
|
|
2551
|
-
duplicateConfig: [],
|
|
2552
|
-
unprotectedPages: [],
|
|
2553
|
-
directSupabaseAuth: [],
|
|
2554
|
-
providerSetupIssues: [],
|
|
2555
|
-
viteConfigIssues: [],
|
|
2556
|
-
routerSetupIssues: [],
|
|
2557
|
-
unnecessaryWrappers: [],
|
|
2558
|
-
appDiscoveryIssues: []
|
|
2559
|
-
};
|
|
2560
|
-
|
|
2561
|
-
files.forEach(file => {
|
|
2562
|
-
try {
|
|
2563
|
-
const violations = scanFile(file, manifest);
|
|
2564
|
-
allViolations.restrictedImports.push(...violations.restrictedImports);
|
|
2565
|
-
allViolations.duplicateComponents.push(...violations.duplicateComponents);
|
|
2566
|
-
allViolations.duplicateHooks.push(...violations.duplicateHooks);
|
|
2567
|
-
allViolations.duplicateUtils.push(...violations.duplicateUtils);
|
|
2568
|
-
allViolations.suggestions.push(...violations.suggestions);
|
|
2569
|
-
allViolations.customAuthCode.push(...violations.customAuthCode);
|
|
2570
|
-
allViolations.duplicateConfig.push(...violations.duplicateConfig);
|
|
2571
|
-
allViolations.unprotectedPages.push(...violations.unprotectedPages);
|
|
2572
|
-
allViolations.directSupabaseAuth.push(...violations.directSupabaseAuth);
|
|
2573
|
-
allViolations.providerSetupIssues.push(...violations.providerSetupIssues);
|
|
2574
|
-
allViolations.viteConfigIssues.push(...violations.viteConfigIssues);
|
|
2575
|
-
allViolations.routerSetupIssues.push(...violations.routerSetupIssues);
|
|
2576
|
-
allViolations.unnecessaryWrappers.push(...violations.unnecessaryWrappers);
|
|
2577
|
-
allViolations.appDiscoveryIssues.push(...violations.appDiscoveryIssues);
|
|
2578
|
-
} catch (error) {
|
|
2579
|
-
console.error(`${colors.red}Error scanning ${file}: ${error.message}${colors.reset}`);
|
|
2580
2580
|
}
|
|
2581
|
-
|
|
2582
|
-
|
|
2583
|
-
|
|
2584
|
-
|
|
2585
|
-
|
|
2586
|
-
|
|
2587
|
-
|
|
2588
|
-
|
|
2589
|
-
|
|
2590
|
-
|
|
2591
|
-
|
|
2592
|
-
|
|
2593
|
-
|
|
2594
|
-
|
|
2595
|
-
|
|
2581
|
+
|
|
2582
|
+
// Convert violations to issues/warnings/suggestions format
|
|
2583
|
+
const issues = [
|
|
2584
|
+
...allViolations.restrictedImports.map(v => ({
|
|
2585
|
+
type: 'restricted-import',
|
|
2586
|
+
file: v.file,
|
|
2587
|
+
line: v.line,
|
|
2588
|
+
message: `Restricted import: ${v.module} - ${v.reason}`,
|
|
2589
|
+
recommendation: `Use pace-core alternative instead`
|
|
2590
|
+
})),
|
|
2591
|
+
...allViolations.duplicateComponents.map(v => ({
|
|
2592
|
+
type: 'duplicate-component',
|
|
2593
|
+
file: v.file,
|
|
2594
|
+
message: `Duplicate component: ${v.component}`,
|
|
2595
|
+
recommendation: `Use ${v.component} from '@jmruthers/pace-core' instead`
|
|
2596
|
+
})),
|
|
2597
|
+
...allViolations.duplicateHooks.map(v => ({
|
|
2598
|
+
type: 'duplicate-hook',
|
|
2599
|
+
file: v.file,
|
|
2600
|
+
message: `Duplicate hook: ${v.hook}`,
|
|
2601
|
+
recommendation: `Use ${v.hook} from '@jmruthers/pace-core' instead`
|
|
2602
|
+
})),
|
|
2603
|
+
...allViolations.duplicateUtils.map(v => ({
|
|
2604
|
+
type: 'duplicate-util',
|
|
2605
|
+
file: v.file,
|
|
2606
|
+
message: `Duplicate util: ${v.util}`,
|
|
2607
|
+
recommendation: `Use ${v.util} from '@jmruthers/pace-core' instead`
|
|
2608
|
+
})),
|
|
2609
|
+
...allViolations.customAuthCode.filter(v => !v.severity || v.severity === 'error').map(v => ({
|
|
2610
|
+
type: 'custom-auth-code',
|
|
2611
|
+
file: v.file,
|
|
2612
|
+
line: v.line,
|
|
2613
|
+
message: `${v.type}: ${v.name} - ${v.reason}`,
|
|
2614
|
+
recommendation: v.replacement || 'Use pace-core APIs instead'
|
|
2615
|
+
})),
|
|
2616
|
+
...allViolations.directSupabaseClient.map(v => ({
|
|
2617
|
+
type: 'direct-supabase-client',
|
|
2618
|
+
file: v.file,
|
|
2619
|
+
line: v.line,
|
|
2620
|
+
message: `Direct Supabase client usage: ${v.reason}`,
|
|
2621
|
+
recommendation: v.recommendation || 'Use useSecureSupabase() instead'
|
|
2622
|
+
})),
|
|
2623
|
+
...allViolations.providerSetupIssues.map(v => ({
|
|
2624
|
+
type: 'provider-setup',
|
|
2625
|
+
file: v.file,
|
|
2626
|
+
line: v.line,
|
|
2627
|
+
message: v.issue || v.reason,
|
|
2628
|
+
recommendation: v.recommendation
|
|
2629
|
+
})),
|
|
2630
|
+
...allViolations.viteConfigIssues.map(v => ({
|
|
2631
|
+
type: 'vite-config',
|
|
2632
|
+
file: v.file,
|
|
2633
|
+
line: v.line,
|
|
2634
|
+
message: v.issue,
|
|
2635
|
+
recommendation: v.recommendation
|
|
2636
|
+
})),
|
|
2637
|
+
...allViolations.routerSetupIssues.map(v => ({
|
|
2638
|
+
type: 'router-setup',
|
|
2639
|
+
file: v.file,
|
|
2640
|
+
line: v.line,
|
|
2641
|
+
message: v.issue,
|
|
2642
|
+
recommendation: v.recommendation
|
|
2643
|
+
}))
|
|
2644
|
+
];
|
|
2645
|
+
|
|
2646
|
+
const warnings = [
|
|
2647
|
+
...allViolations.customAuthCode.filter(v => v.severity === 'warning').map(v => ({
|
|
2648
|
+
type: 'custom-auth-code',
|
|
2649
|
+
file: v.file,
|
|
2650
|
+
line: v.line,
|
|
2651
|
+
message: `${v.type}: ${v.name} - ${v.reason}`,
|
|
2652
|
+
recommendation: v.replacement || 'Consider using pace-core APIs'
|
|
2653
|
+
})),
|
|
2654
|
+
...allViolations.directSupabaseAuth.map(v => ({
|
|
2655
|
+
type: 'direct-supabase-auth',
|
|
2656
|
+
file: v.file,
|
|
2657
|
+
line: v.line,
|
|
2658
|
+
message: `Direct Supabase auth usage: ${v.reason}`,
|
|
2659
|
+
recommendation: v.recommendation || 'Use useUnifiedAuth() instead'
|
|
2660
|
+
})),
|
|
2661
|
+
...allViolations.deprecatedSecureDataAccess.map(v => ({
|
|
2662
|
+
type: 'deprecated-api',
|
|
2663
|
+
file: v.file,
|
|
2664
|
+
line: v.line,
|
|
2665
|
+
message: `Deprecated API: ${v.method} - ${v.reason}`,
|
|
2666
|
+
recommendation: v.recommendation || 'Migrate to useSecureSupabase()'
|
|
2667
|
+
})),
|
|
2668
|
+
...allViolations.unnecessaryWrappers.map(v => ({
|
|
2669
|
+
type: 'unnecessary-wrapper',
|
|
2670
|
+
file: v.file,
|
|
2671
|
+
line: v.line,
|
|
2672
|
+
message: `Unnecessary wrapper: ${v.component} wraps ${v.wrappedComponent}`,
|
|
2673
|
+
recommendation: v.recommendation || 'Remove wrapper and use component directly'
|
|
2674
|
+
})),
|
|
2675
|
+
...allViolations.appDiscoveryIssues.filter(v => v.severity === 'warning').map(v => ({
|
|
2676
|
+
type: 'app-discovery',
|
|
2677
|
+
file: v.file,
|
|
2678
|
+
message: v.issue || v.reason,
|
|
2679
|
+
recommendation: v.recommendation
|
|
2680
|
+
}))
|
|
2681
|
+
];
|
|
2682
|
+
|
|
2683
|
+
const suggestions = [
|
|
2684
|
+
...allViolations.suggestions.map(v => ({
|
|
2685
|
+
type: 'suggestion',
|
|
2686
|
+
file: v.file,
|
|
2687
|
+
message: v.suggestion
|
|
2688
|
+
})),
|
|
2689
|
+
...allViolations.appDiscoveryIssues.filter(v => v.severity === 'info').map(v => ({
|
|
2690
|
+
type: 'app-discovery',
|
|
2691
|
+
file: v.file,
|
|
2692
|
+
message: v.issue || v.reason,
|
|
2693
|
+
recommendation: v.recommendation
|
|
2694
|
+
}))
|
|
2695
|
+
];
|
|
2696
|
+
|
|
2697
|
+
return {
|
|
2698
|
+
issues,
|
|
2699
|
+
warnings,
|
|
2700
|
+
suggestions,
|
|
2701
|
+
violations: allViolations
|
|
2702
|
+
};
|
|
2596
2703
|
}
|
|
2597
|
-
}
|
|
2598
|
-
|
|
2599
|
-
module.exports = {
|
|
2600
|
-
main,
|
|
2601
|
-
scanFile,
|
|
2602
|
-
generateReport,
|
|
2603
|
-
loadManifest,
|
|
2604
|
-
findProjectRoot,
|
|
2605
|
-
findSourceFiles
|
|
2606
2704
|
};
|
|
2607
2705
|
|
|
2706
|
+
module.exports = complianceCheck;
|