@jmruthers/pace-core 0.6.5 → 0.6.7
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 +104 -0
- package/README.md +5 -403
- package/audit-tool/00-dependencies.cjs +394 -0
- package/audit-tool/audits/01-pace-core-compliance.cjs +556 -0
- package/audit-tool/audits/02-project-structure.cjs +255 -0
- package/audit-tool/audits/03-architecture.cjs +196 -0
- package/audit-tool/audits/04-code-quality.cjs +149 -0
- package/audit-tool/audits/05-styling.cjs +224 -0
- package/audit-tool/audits/06-security-rbac.cjs +544 -0
- package/audit-tool/audits/07-api-tech-stack.cjs +301 -0
- package/audit-tool/audits/08-testing-documentation.cjs +202 -0
- package/audit-tool/audits/09-operations.cjs +208 -0
- package/audit-tool/index.cjs +291 -0
- package/audit-tool/utils/code-utils.cjs +218 -0
- package/audit-tool/utils/file-utils.cjs +230 -0
- package/audit-tool/utils/report-utils.cjs +241 -0
- package/core-usage-manifest.json +93 -0
- package/cursor-rules/00-standards-overview.mdc +156 -0
- package/cursor-rules/01-pace-core-compliance.mdc +586 -0
- package/cursor-rules/02-project-structure.mdc +42 -4
- package/cursor-rules/{03-solid-principles.mdc → 03-architecture.mdc} +126 -10
- package/cursor-rules/04-code-quality.mdc +419 -0
- package/cursor-rules/{08-markup-quality.mdc → 05-styling.mdc} +104 -34
- package/cursor-rules/06-security-rbac.mdc +518 -0
- package/cursor-rules/07-api-tech-stack.mdc +377 -0
- package/cursor-rules/08-testing-documentation.mdc +324 -0
- package/cursor-rules/09-operations.mdc +365 -0
- package/dist/{AuthService-Cb34EQs3.d.ts → AuthService-DmfO5rGS.d.ts} +10 -0
- package/dist/DataTable-7PMH7XN7.js +15 -0
- package/dist/{DataTable-BMRU8a1j.d.ts → DataTable-DRUIgtUH.d.ts} +1 -1
- package/dist/{PublicPageProvider-QTFVrL-Z.d.ts → PublicPageProvider-DlsCaR5v.d.ts} +33 -72
- package/dist/UnifiedAuthProvider-ZT6TIGM7.js +7 -0
- package/dist/api-Y4MQWOFW.js +4 -0
- package/dist/audit-MYQXYZFU.js +3 -0
- package/dist/{chunk-DGUM43GV.js → chunk-3RG5ZIWI.js} +1 -4
- package/dist/{chunk-QXHPKYJV.js → chunk-4SXLQIZO.js} +1 -26
- package/dist/{chunk-UPPMRMYG.js → chunk-5X4QLXRG.js} +73 -151
- package/dist/chunk-6F3IILHI.js +62 -0
- package/dist/{chunk-E66EQZE6.js → chunk-6GLLNA6U.js} +3 -9
- package/dist/{chunk-ZSAAAMVR.js → chunk-6QYDGKQY.js} +1 -4
- package/dist/{chunk-FMUCXFII.js → chunk-7ILTDCL2.js} +9 -5
- package/dist/{chunk-M43Y4SSO.js → chunk-A3W6LW53.js} +15 -13
- package/dist/{chunk-63FOKYGO.js → chunk-AHU7G2R5.js} +2 -11
- package/dist/{chunk-HU2C6SSC.js → chunk-BM4CQ5P3.js} +606 -559
- package/dist/chunk-C7NSAPTL.js +1 -0
- package/dist/{chunk-J36DSWQK.js → chunk-FEJLJNWA.js} +7 -41
- package/dist/{chunk-IHB5DR3H.js → chunk-FTCRZOG2.js} +188 -387
- package/dist/{chunk-G37KK66H.js → chunk-FYHN4DD5.js} +60 -19
- package/dist/chunk-GHYHJTYV.js +994 -0
- package/dist/{chunk-VBXEHIUJ.js → chunk-HF6O3O37.js} +6 -88
- package/dist/{chunk-FFQEQTNW.js → chunk-IUBRCBSY.js} +134 -45
- package/dist/{chunk-6COVEUS7.js → chunk-JGWDVX64.js} +983 -1034
- package/dist/{chunk-RGAWHO7N.js → chunk-L4XMVJKY.js} +77 -222
- package/dist/chunk-MBADTM7L.js +64 -0
- package/dist/{chunk-M7MPQISP.js → chunk-OJ4SKRSV.js} +3 -16
- package/dist/{chunk-IVOFDYWT.js → chunk-Q7Q7V5NV.js} +2109 -1604
- package/dist/{chunk-JGRYX5UX.js → chunk-S7DKJPLT.js} +29 -58
- package/dist/{chunk-PWLANIRT.js → chunk-TTRFSOKR.js} +1 -7
- package/dist/{chunk-5DRSZLL2.js → chunk-UH3NTO3F.js} +1 -6
- package/dist/{chunk-NTM7ZSB6.js → chunk-VBCS3DUA.js} +261 -168
- package/dist/{chunk-EFN2EIMK.js → chunk-ZFYPMX46.js} +271 -87
- package/dist/{chunk-L4OXEN46.js → chunk-ZKAWKYT4.js} +10 -24
- package/dist/components.d.ts +7 -5
- package/dist/components.js +46 -257
- package/dist/{database.generated-CzIvgcPu.d.ts → database.generated-CcnC_DRc.d.ts} +4795 -3691
- package/dist/eslint-rules/index.cjs +35 -0
- package/{src/eslint-rules/pace-core-compliance.cjs → dist/eslint-rules/rules/01-pace-core-compliance.cjs} +234 -235
- package/dist/eslint-rules/rules/04-code-quality.cjs +290 -0
- package/dist/eslint-rules/rules/05-styling.cjs +61 -0
- package/dist/eslint-rules/rules/06-security-rbac.cjs +806 -0
- package/dist/eslint-rules/rules/07-api-tech-stack.cjs +263 -0
- package/dist/eslint-rules/rules/08-testing.cjs +94 -0
- package/dist/eslint-rules/utils/helpers.cjs +42 -0
- package/dist/eslint-rules/utils/manifest-loader.cjs +75 -0
- package/dist/hooks.d.ts +6 -6
- package/dist/hooks.js +62 -172
- package/dist/icons/index.d.ts +1 -0
- package/dist/icons/index.js +1 -0
- package/dist/index.d.ts +12 -11
- package/dist/index.js +67 -660
- package/dist/providers.d.ts +2 -2
- package/dist/providers.js +8 -35
- package/dist/rbac/eslint-rules.d.ts +46 -44
- package/dist/rbac/eslint-rules.js +7 -4
- package/dist/rbac/index.d.ts +109 -586
- package/dist/rbac/index.js +14 -207
- package/dist/styles/index.js +2 -12
- package/dist/theming/runtime.d.ts +14 -1
- package/dist/theming/runtime.js +3 -19
- package/dist/{timezone-CHhWg6b4.d.ts → timezone-BZe_eUxx.d.ts} +175 -1
- package/dist/{types-CkbwOr4Y.d.ts → types-DXstZpNI.d.ts} +4 -17
- package/dist/types-t9H8qKRw.d.ts +55 -0
- package/dist/types.d.ts +1 -1
- package/dist/types.js +7 -94
- package/dist/{usePublicRouteParams-ClnV4tnv.d.ts → usePublicRouteParams-MamNgwqe.d.ts} +20 -20
- package/dist/utils.d.ts +24 -117
- package/dist/utils.js +54 -392
- package/docs/README.md +17 -7
- package/docs/api/README.md +4 -402
- package/docs/api/modules.md +301 -871
- package/docs/api-reference/components.md +21 -21
- package/docs/api-reference/deprecated.md +31 -6
- package/docs/api-reference/hooks.md +80 -80
- package/docs/api-reference/rpc-functions.md +78 -3
- package/docs/api-reference/types.md +1 -1
- package/docs/api-reference/utilities.md +1 -1
- package/docs/architecture/README.md +1 -1
- package/docs/core-concepts/events.md +3 -3
- package/docs/core-concepts/organisations.md +6 -6
- package/docs/core-concepts/permissions.md +6 -6
- package/docs/documentation-index.md +12 -18
- package/docs/getting-started/cursor-rules.md +3 -23
- package/docs/getting-started/dependencies.md +650 -0
- package/docs/getting-started/documentation-index.md +1 -1
- package/docs/getting-started/examples/README.md +4 -4
- package/docs/getting-started/examples/full-featured-app.md +1 -1
- package/docs/getting-started/faq.md +2 -2
- package/docs/getting-started/installation-guide.md +20 -7
- package/docs/getting-started/quick-reference.md +4 -4
- package/docs/getting-started/quick-start.md +23 -12
- package/docs/implementation-guides/authentication.md +15 -15
- package/docs/implementation-guides/component-styling.md +1 -1
- package/docs/implementation-guides/data-tables.md +126 -33
- package/docs/implementation-guides/datatable-rbac-usage.md +1 -1
- package/docs/implementation-guides/dynamic-colors.md +3 -3
- package/docs/implementation-guides/file-upload-storage.md +2 -2
- package/docs/implementation-guides/hierarchical-datatable.md +40 -60
- package/docs/implementation-guides/inactivity-tracking.md +3 -3
- package/docs/implementation-guides/large-datasets.md +3 -2
- package/docs/implementation-guides/organisation-security.md +2 -2
- package/docs/implementation-guides/performance.md +2 -2
- package/docs/implementation-guides/permission-enforcement.md +5 -1
- package/docs/migration/V0.3.44_organisation-context-timing-fix.md +1 -1
- package/docs/migration/V0.4.0_rbac-migration.md +6 -6
- package/docs/rbac/MIGRATION_GUIDE.md +819 -0
- package/docs/rbac/RBAC_CONTRACT.md +724 -0
- package/docs/rbac/README.md +17 -8
- package/docs/rbac/advanced-patterns.md +6 -6
- package/docs/rbac/api-reference.md +20 -20
- package/docs/rbac/edge-functions-guide.md +376 -0
- package/docs/rbac/event-based-apps.md +3 -3
- package/docs/rbac/examples.md +41 -41
- package/docs/rbac/getting-started.md +37 -37
- package/docs/rbac/performance.md +1 -1
- package/docs/rbac/quick-start.md +52 -52
- package/docs/rbac/secure-client-protection.md +1 -35
- package/docs/rbac/troubleshooting.md +1 -1
- package/docs/security/README.md +5 -5
- package/docs/standards/0-standards-overview.md +220 -0
- package/docs/standards/1-pace-core-compliance-standards.md +986 -0
- package/docs/standards/2-project-structure-standards.md +949 -0
- package/docs/standards/3-architecture-standards.md +606 -0
- package/docs/standards/4-code-quality-standards.md +728 -0
- package/docs/standards/5-styling-standards.md +348 -0
- package/docs/standards/{07-rbac-and-rls-standard.md → 6-security-rbac-standards.md} +269 -66
- package/docs/standards/7-api-tech-stack-standards.md +662 -0
- package/docs/standards/8-testing-documentation-standards.md +401 -0
- package/docs/standards/9-operations-standards.md +1102 -0
- package/docs/standards/README.md +185 -57
- package/docs/troubleshooting/README.md +4 -4
- package/docs/troubleshooting/common-issues.md +2 -2
- package/docs/troubleshooting/debugging.md +9 -9
- package/docs/troubleshooting/migration.md +4 -4
- package/docs/troubleshooting/organisation-context-setup.md +42 -19
- package/eslint-config-pace-core.cjs +33 -6
- package/package.json +35 -23
- package/scripts/install-cursor-rules.cjs +25 -6
- package/scripts/install-eslint-config.cjs +284 -0
- package/src/__tests__/fixtures/supabase.ts +1 -1
- package/src/__tests__/helpers/__tests__/component-test-utils.test.tsx +3 -3
- package/src/__tests__/helpers/__tests__/optimized-test-setup.test.ts +1 -1
- package/src/__tests__/helpers/__tests__/supabaseMock.test.ts +1 -1
- package/src/__tests__/helpers/__tests__/test-providers.test.tsx +2 -2
- package/src/__tests__/helpers/__tests__/test-utils.test.tsx +13 -13
- package/src/__tests__/helpers/component-test-utils.tsx +1 -1
- package/src/__tests__/helpers/supabaseMock.ts +2 -2
- package/src/__tests__/integration/UserProfile.test.tsx +14 -14
- package/src/__tests__/public-recipe-view.test.ts +38 -9
- package/src/__tests__/rbac/PagePermissionGuard.test.tsx +6 -6
- package/src/__tests__/templates/accessibility.test.template.tsx +9 -9
- package/src/__tests__/templates/component.test.template.tsx +18 -15
- package/src/components/Button/Button.tsx +5 -1
- package/src/components/Calendar/Calendar.tsx +201 -47
- package/src/components/ContextSelector/ContextSelector.tsx +106 -119
- package/src/components/DataTable/AUDIT_REPORT.md +293 -0
- package/src/components/DataTable/__tests__/DataTableCore.test.tsx +10 -2
- package/src/components/DataTable/__tests__/a11y.basic.test.tsx +10 -4
- package/src/components/DataTable/__tests__/test-utils/sharedTestUtils.tsx +9 -9
- package/src/components/DataTable/components/ColumnFilter.tsx +63 -74
- package/src/components/DataTable/components/ColumnVisibilityDropdown.tsx +43 -41
- package/src/components/DataTable/components/DataTableCore.tsx +186 -13
- package/src/components/DataTable/components/DataTableErrorBoundary.tsx +9 -11
- package/src/components/DataTable/components/DataTableLayout.tsx +35 -21
- package/src/components/DataTable/components/EditFields.tsx +23 -3
- package/src/components/DataTable/components/EditableRow.tsx +12 -9
- package/src/components/DataTable/components/EmptyState.tsx +10 -9
- package/src/components/DataTable/components/FilterRow.tsx +2 -4
- package/src/components/DataTable/components/ImportModal.tsx +124 -126
- package/src/components/DataTable/components/LoadingState.tsx +5 -6
- package/src/components/DataTable/components/RowComponent.tsx +12 -0
- package/src/components/DataTable/components/SortIndicator.tsx +50 -0
- package/src/components/DataTable/components/__tests__/COVERAGE_NOTE.md +4 -4
- package/src/components/DataTable/components/__tests__/ColumnFilter.test.tsx +23 -82
- package/src/components/DataTable/components/__tests__/DataTableErrorBoundary.test.tsx +37 -9
- package/src/components/DataTable/components/__tests__/EmptyState.test.tsx +7 -4
- package/src/components/DataTable/components/__tests__/FilterRow.test.tsx +12 -4
- package/src/components/DataTable/components/__tests__/LoadingState.test.tsx +41 -27
- package/src/components/DataTable/components/hooks/usePermissionTracking.ts +0 -4
- package/src/components/DataTable/components/index.ts +2 -1
- package/src/components/DataTable/hooks/__tests__/useDataTableState.test.ts +51 -47
- package/src/components/DataTable/hooks/useDataTablePermissions.ts +24 -21
- package/src/components/DataTable/hooks/useDataTableState.ts +125 -9
- package/src/components/DataTable/hooks/useTableColumns.ts +40 -2
- package/src/components/DataTable/hooks/useTableHandlers.ts +11 -0
- package/src/components/DataTable/types.ts +5 -18
- package/src/components/DataTable/utils/a11yUtils.ts +17 -0
- package/src/components/DatePickerWithTimezone/DatePickerWithTimezone.test.tsx +2 -1
- package/src/components/DatePickerWithTimezone/DatePickerWithTimezone.tsx +11 -15
- package/src/components/DateTimeField/DateTimeField.tsx +10 -9
- package/src/components/Dialog/Dialog.test.tsx +128 -104
- package/src/components/Dialog/Dialog.tsx +742 -24
- package/src/components/ErrorBoundary/ErrorBoundary.tsx +77 -79
- package/src/components/FileDisplay/FileDisplay.test.tsx +4 -2
- package/src/components/FileDisplay/FileDisplay.tsx +23 -17
- package/src/components/FileUpload/FileUpload.test.tsx +52 -14
- package/src/components/FileUpload/FileUpload.tsx +112 -130
- package/src/components/Form/Form.test.tsx +6 -8
- package/src/components/Form/Form.tsx +365 -4
- package/src/components/NavigationMenu/NavigationMenu.test.tsx +14 -13
- package/src/components/NavigationMenu/useNavigationFiltering.ts +11 -21
- package/src/components/PaceAppLayout/PaceAppLayout.test.tsx +6 -4
- package/src/components/PaceAppLayout/PaceAppLayout.tsx +11 -15
- package/src/components/PaceLoginPage/PaceLoginPage.test.tsx +108 -61
- package/src/components/PaceLoginPage/PaceLoginPage.tsx +27 -3
- package/src/components/Progress/Progress.tsx +2 -4
- package/src/components/ProtectedRoute/ProtectedRoute.tsx +8 -8
- package/src/components/Select/Select.tsx +109 -98
- package/src/components/Select/types.ts +4 -1
- package/src/components/UserMenu/UserMenu.tsx +9 -6
- package/src/hooks/__tests__/ServiceHooks.test.tsx +16 -16
- package/src/hooks/__tests__/hooks.integration.test.tsx +55 -57
- package/src/hooks/__tests__/useAppConfig.unit.test.ts +129 -67
- package/src/hooks/__tests__/useFocusTrap.unit.test.tsx +97 -97
- package/src/hooks/__tests__/usePublicEvent.simple.test.ts +149 -67
- package/src/hooks/__tests__/usePublicEvent.test.ts +149 -79
- package/src/hooks/__tests__/usePublicEvent.unit.test.ts +158 -109
- package/src/hooks/__tests__/useSessionDraft.test.ts +163 -0
- package/src/hooks/__tests__/useSessionRestoration.unit.test.tsx +10 -5
- package/src/hooks/public/usePublicEvent.ts +67 -195
- package/src/hooks/public/usePublicEventLogo.test.ts +70 -17
- package/src/hooks/public/usePublicEventLogo.ts +24 -14
- package/src/hooks/public/usePublicFileDisplay.ts +2 -2
- package/src/hooks/public/usePublicRouteParams.ts +5 -5
- package/src/hooks/useAppConfig.ts +28 -26
- package/src/hooks/useEventTheme.test.ts +217 -239
- package/src/hooks/useEventTheme.ts +16 -28
- package/src/hooks/useFileDisplay.ts +2 -2
- package/src/hooks/useOrganisationPermissions.ts +5 -7
- package/src/hooks/useQueryCache.ts +0 -1
- package/src/hooks/useSessionDraft.ts +380 -0
- package/src/hooks/useSessionRestoration.ts +3 -1
- package/src/icons/index.ts +27 -0
- package/src/index.ts +5 -0
- package/src/providers/OrganisationProvider.tsx +23 -14
- package/src/providers/UnifiedAuthProvider.smoke.test.tsx +21 -21
- package/src/providers/__tests__/AuthProvider.test.tsx +21 -21
- package/src/providers/__tests__/EventProvider.test.tsx +61 -61
- package/src/providers/__tests__/InactivityProvider.test.tsx +56 -56
- package/src/providers/__tests__/OrganisationProvider.test.tsx +75 -75
- package/src/providers/__tests__/ProviderLifecycle.test.tsx +37 -37
- package/src/providers/__tests__/UnifiedAuthProvider.test.tsx +103 -103
- package/src/providers/services/EventServiceProvider.tsx +1 -24
- package/src/providers/services/UnifiedAuthProvider.tsx +5 -48
- package/src/providers/services/__tests__/AuthServiceProvider.integration.test.tsx +7 -7
- package/src/providers/services/__tests__/UnifiedAuthProvider.integration.test.tsx +13 -10
- package/src/rbac/__tests__/adapters.comprehensive.test.tsx +7 -457
- package/src/rbac/__tests__/auth-rbac.e2e.test.tsx +33 -7
- package/src/rbac/adapters.tsx +7 -295
- package/src/rbac/api.test.ts +44 -56
- package/src/rbac/api.ts +10 -17
- package/src/rbac/cache-invalidation.ts +0 -1
- package/src/rbac/compliance/index.ts +10 -0
- package/src/rbac/compliance/pattern-detector.ts +553 -0
- package/src/rbac/compliance/runtime-compliance.ts +22 -0
- package/src/rbac/components/AccessDenied.tsx +150 -0
- package/src/rbac/components/NavigationGuard.tsx +12 -20
- package/src/rbac/components/PagePermissionGuard.tsx +4 -24
- package/src/rbac/components/__tests__/NavigationGuard.test.tsx +21 -8
- package/src/rbac/components/index.ts +3 -41
- package/src/rbac/eslint-rules.js +1 -1
- package/src/rbac/hooks/index.ts +0 -3
- package/src/rbac/hooks/permissions/index.ts +0 -3
- package/src/rbac/hooks/permissions/useAccessLevel.ts +4 -8
- package/src/rbac/hooks/usePermissions.ts +0 -3
- package/src/rbac/hooks/useResolvedScope.test.ts +57 -47
- package/src/rbac/hooks/useResolvedScope.ts +58 -140
- package/src/rbac/hooks/useResourcePermissions.test.ts +124 -38
- package/src/rbac/hooks/useResourcePermissions.ts +139 -48
- package/src/rbac/hooks/useRoleManagement.test.ts +65 -22
- package/src/rbac/hooks/useRoleManagement.ts +147 -19
- package/src/rbac/hooks/useSecureSupabase.ts +4 -8
- package/src/rbac/index.ts +7 -9
- package/src/rbac/utils/contextValidator.ts +9 -7
- package/src/services/AuthService.ts +130 -18
- package/src/services/EventService.ts +4 -97
- package/src/services/InactivityService.ts +16 -0
- package/src/services/OrganisationService.ts +7 -44
- package/src/services/__tests__/OrganisationService.test.ts +26 -8
- package/src/services/base/BaseService.ts +0 -3
- package/src/styles/core.css +7 -0
- package/src/theming/__tests__/parseEventColours.test.ts +9 -3
- package/src/theming/parseEventColours.ts +22 -10
- package/src/types/database.generated.ts +4733 -3809
- package/src/utils/__tests__/lazyLoad.unit.test.tsx +42 -39
- package/src/utils/__tests__/organisationContext.unit.test.ts +9 -10
- package/src/utils/context/organisationContext.test.ts +13 -28
- package/src/utils/context/organisationContext.ts +21 -52
- package/src/utils/dynamic/dynamicUtils.ts +1 -1
- package/src/utils/file-reference/index.ts +39 -15
- package/src/utils/formatting/formatDateTime.test.ts +3 -2
- package/src/utils/google-places/loadGoogleMapsScript.ts +29 -4
- package/src/utils/index.ts +4 -1
- package/src/utils/persistence/__tests__/keyDerivation.test.ts +135 -0
- package/src/utils/persistence/__tests__/sensitiveFieldDetection.test.ts +123 -0
- package/src/utils/persistence/keyDerivation.ts +304 -0
- package/src/utils/persistence/sensitiveFieldDetection.ts +212 -0
- package/src/utils/security/secureStorage.ts +5 -5
- package/src/utils/storage/README.md +1 -1
- package/src/utils/storage/helpers.ts +3 -3
- package/src/utils/supabase/createBaseClient.ts +147 -0
- package/src/utils/timezone/timezone.test.ts +1 -2
- package/src/utils/timezone/timezone.ts +1 -1
- package/src/utils/validation/csrf.ts +4 -4
- package/cursor-rules/00-pace-core-compliance.mdc +0 -331
- package/cursor-rules/01-standards-compliance.mdc +0 -244
- package/cursor-rules/04-testing-standards.mdc +0 -268
- package/cursor-rules/05-bug-reports-and-features.mdc +0 -246
- package/cursor-rules/06-code-quality.mdc +0 -309
- package/cursor-rules/07-tech-stack-compliance.mdc +0 -214
- package/cursor-rules/CHANGELOG.md +0 -119
- package/cursor-rules/README.md +0 -192
- package/dist/DataTable-AOVNCPTX.js +0 -175
- package/dist/DataTable-AOVNCPTX.js.map +0 -1
- package/dist/UnifiedAuthProvider-4SBX4LU5.js +0 -18
- package/dist/UnifiedAuthProvider-4SBX4LU5.js.map +0 -1
- package/dist/api-O6HTBX5Y.js +0 -52
- package/dist/api-O6HTBX5Y.js.map +0 -1
- package/dist/audit-V53FV5AG.js +0 -17
- package/dist/audit-V53FV5AG.js.map +0 -1
- package/dist/chunk-5DRSZLL2.js.map +0 -1
- package/dist/chunk-63FOKYGO.js.map +0 -1
- package/dist/chunk-6COVEUS7.js.map +0 -1
- package/dist/chunk-AFVQODI2.js +0 -263
- package/dist/chunk-AFVQODI2.js.map +0 -1
- package/dist/chunk-DGUM43GV.js.map +0 -1
- package/dist/chunk-E66EQZE6.js.map +0 -1
- package/dist/chunk-EFN2EIMK.js.map +0 -1
- package/dist/chunk-FFQEQTNW.js.map +0 -1
- package/dist/chunk-FMUCXFII.js.map +0 -1
- package/dist/chunk-G37KK66H.js.map +0 -1
- package/dist/chunk-G7QEZTYQ.js +0 -2053
- package/dist/chunk-G7QEZTYQ.js.map +0 -1
- package/dist/chunk-HU2C6SSC.js.map +0 -1
- package/dist/chunk-IHB5DR3H.js.map +0 -1
- package/dist/chunk-IVOFDYWT.js.map +0 -1
- package/dist/chunk-J36DSWQK.js.map +0 -1
- package/dist/chunk-JGRYX5UX.js.map +0 -1
- package/dist/chunk-KQCRWDSA.js +0 -1
- package/dist/chunk-KQCRWDSA.js.map +0 -1
- package/dist/chunk-L4OXEN46.js.map +0 -1
- package/dist/chunk-LMC26NLJ.js +0 -84
- package/dist/chunk-LMC26NLJ.js.map +0 -1
- package/dist/chunk-M43Y4SSO.js.map +0 -1
- package/dist/chunk-M7MPQISP.js.map +0 -1
- package/dist/chunk-NTM7ZSB6.js.map +0 -1
- package/dist/chunk-PWLANIRT.js.map +0 -1
- package/dist/chunk-QXHPKYJV.js.map +0 -1
- package/dist/chunk-RGAWHO7N.js.map +0 -1
- package/dist/chunk-UPPMRMYG.js.map +0 -1
- package/dist/chunk-VBXEHIUJ.js.map +0 -1
- package/dist/chunk-ZSAAAMVR.js.map +0 -1
- package/dist/components.js.map +0 -1
- package/dist/contextValidator-5OGXSPKS.js +0 -9
- package/dist/contextValidator-5OGXSPKS.js.map +0 -1
- package/dist/eslint-rules/pace-core-compliance.cjs +0 -510
- package/dist/hooks.js.map +0 -1
- package/dist/index.js.map +0 -1
- package/dist/providers.js.map +0 -1
- package/dist/rbac/eslint-rules.js.map +0 -1
- package/dist/rbac/index.js.map +0 -1
- package/dist/styles/index.js.map +0 -1
- package/dist/theming/runtime.js.map +0 -1
- package/dist/types.js.map +0 -1
- package/dist/utils.js.map +0 -1
- package/docs/best-practices/README.md +0 -472
- package/docs/best-practices/accessibility.md +0 -601
- package/docs/best-practices/common-patterns.md +0 -516
- package/docs/best-practices/deployment.md +0 -1103
- package/docs/best-practices/performance.md +0 -1328
- package/docs/best-practices/security.md +0 -940
- package/docs/best-practices/testing.md +0 -1034
- package/docs/rbac/compliance/compliance-guide.md +0 -544
- package/docs/standards/01-architecture-standard.md +0 -44
- package/docs/standards/02-api-and-rpc-standard.md +0 -39
- package/docs/standards/03-component-standard.md +0 -32
- package/docs/standards/04-code-style-standard.md +0 -32
- package/docs/standards/05-security-standard.md +0 -44
- package/docs/standards/06-testing-and-docs-standard.md +0 -29
- package/docs/standards/pace-core-compliance.md +0 -432
- package/scripts/audit/core/checks/accessibility.cjs +0 -197
- package/scripts/audit/core/checks/api-usage.cjs +0 -191
- package/scripts/audit/core/checks/bundle.cjs +0 -142
- package/scripts/audit/core/checks/compliance.cjs +0 -2706
- package/scripts/audit/core/checks/config.cjs +0 -54
- package/scripts/audit/core/checks/coverage.cjs +0 -84
- package/scripts/audit/core/checks/dependencies.cjs +0 -994
- package/scripts/audit/core/checks/documentation.cjs +0 -268
- package/scripts/audit/core/checks/environment.cjs +0 -116
- package/scripts/audit/core/checks/error-handling.cjs +0 -340
- package/scripts/audit/core/checks/forms.cjs +0 -172
- package/scripts/audit/core/checks/heuristics.cjs +0 -68
- package/scripts/audit/core/checks/hooks.cjs +0 -334
- package/scripts/audit/core/checks/imports.cjs +0 -244
- package/scripts/audit/core/checks/performance.cjs +0 -325
- package/scripts/audit/core/checks/routes.cjs +0 -117
- package/scripts/audit/core/checks/state.cjs +0 -130
- package/scripts/audit/core/checks/structure.cjs +0 -65
- package/scripts/audit/core/checks/style.cjs +0 -584
- package/scripts/audit/core/checks/testing.cjs +0 -122
- package/scripts/audit/core/checks/typescript.cjs +0 -61
- package/scripts/audit/core/scanner.cjs +0 -199
- package/scripts/audit/core/utils.cjs +0 -137
- package/scripts/audit/index.cjs +0 -223
- package/scripts/audit/reporters/console.cjs +0 -151
- package/scripts/audit/reporters/json.cjs +0 -54
- package/scripts/audit/reporters/markdown.cjs +0 -124
- package/scripts/audit-consuming-app.cjs +0 -86
- package/src/components/DataTable/components/DataTableBody.tsx +0 -454
- package/src/components/DataTable/components/DraggableColumnHeader.tsx +0 -156
- package/src/components/DataTable/components/ExpandButton.tsx +0 -113
- package/src/components/DataTable/components/GroupHeader.tsx +0 -54
- package/src/components/DataTable/components/ViewRowModal.tsx +0 -68
- package/src/components/DataTable/components/VirtualizedDataTable.tsx +0 -525
- package/src/components/DataTable/components/__tests__/ExpandButton.test.tsx +0 -462
- package/src/components/DataTable/components/__tests__/GroupHeader.test.tsx +0 -393
- package/src/components/DataTable/components/__tests__/ViewRowModal.test.tsx +0 -476
- package/src/components/DataTable/components/__tests__/VirtualizedDataTable.test.tsx +0 -128
- package/src/components/DataTable/core/DataTableContext.tsx +0 -216
- package/src/components/DataTable/core/__tests__/DataTableContext.test.tsx +0 -136
- package/src/components/DataTable/hooks/__tests__/useColumnReordering.test.ts +0 -570
- package/src/components/DataTable/hooks/useColumnReordering.ts +0 -123
- package/src/components/DataTable/utils/debugTools.ts +0 -514
- package/src/eslint-rules/pace-core-compliance.js +0 -638
- package/src/rbac/components/EnhancedNavigationMenu.test.tsx +0 -555
- package/src/rbac/components/EnhancedNavigationMenu.tsx +0 -293
- package/src/rbac/components/NavigationProvider.test.tsx +0 -481
- package/src/rbac/components/NavigationProvider.tsx +0 -345
- package/src/rbac/components/PagePermissionProvider.test.tsx +0 -476
- package/src/rbac/components/PagePermissionProvider.tsx +0 -279
- package/src/rbac/components/PermissionEnforcer.tsx +0 -312
- package/src/rbac/components/RoleBasedRouter.tsx +0 -440
- package/src/rbac/components/SecureDataProvider.test.tsx +0 -543
- package/src/rbac/components/SecureDataProvider.tsx +0 -339
- package/src/rbac/components/__tests__/EnhancedNavigationMenu.test.tsx +0 -620
- package/src/rbac/components/__tests__/NavigationProvider.test.tsx +0 -726
- package/src/rbac/components/__tests__/PagePermissionProvider.test.tsx +0 -661
- package/src/rbac/components/__tests__/PermissionEnforcer.test.tsx +0 -881
- package/src/rbac/components/__tests__/RoleBasedRouter.test.tsx +0 -783
- package/src/rbac/components/__tests__/SecureDataProvider.fixed.test.tsx +0 -645
- package/src/rbac/components/__tests__/SecureDataProvider.test.tsx +0 -659
- package/src/rbac/hooks/permissions/useCachedPermissions.ts +0 -79
- package/src/rbac/hooks/permissions/useHasAllPermissions.ts +0 -90
- package/src/rbac/hooks/permissions/useHasAnyPermission.ts +0 -90
|
@@ -1,334 +0,0 @@
|
|
|
1
|
-
#!/usr/bin/env node
|
|
2
|
-
|
|
3
|
-
/**
|
|
4
|
-
* React Hooks Compliance Check Module
|
|
5
|
-
* @package @jmruthers/pace-core
|
|
6
|
-
* @module Audit/Checks/Hooks
|
|
7
|
-
*
|
|
8
|
-
* Checks for:
|
|
9
|
-
* - Hooks called conditionally or after early returns
|
|
10
|
-
* - Hooks called in loops
|
|
11
|
-
* - Missing dependencies in useEffect/useMemo/useCallback
|
|
12
|
-
* - Hooks called in wrong order
|
|
13
|
-
*/
|
|
14
|
-
|
|
15
|
-
const fs = require('fs');
|
|
16
|
-
const { getRelativePath, getLineNumber } = require('../utils.cjs');
|
|
17
|
-
|
|
18
|
-
const hooksCheck = {
|
|
19
|
-
name: 'hooks',
|
|
20
|
-
description: 'React hooks compliance (conditional calls, missing dependencies, etc.)',
|
|
21
|
-
severity: 'error',
|
|
22
|
-
|
|
23
|
-
async run(context) {
|
|
24
|
-
const { projectRoot, files } = context;
|
|
25
|
-
const issues = [];
|
|
26
|
-
const warnings = [];
|
|
27
|
-
const suggestions = [];
|
|
28
|
-
|
|
29
|
-
if (!files || files.length === 0) {
|
|
30
|
-
return { issues, warnings, suggestions };
|
|
31
|
-
}
|
|
32
|
-
|
|
33
|
-
// React hooks that must follow rules
|
|
34
|
-
// Separate actual React hooks from custom hooks
|
|
35
|
-
const actualReactHooks = [
|
|
36
|
-
'useState', 'useEffect', 'useContext', 'useReducer', 'useCallback',
|
|
37
|
-
'useMemo', 'useRef', 'useImperativeHandle', 'useLayoutEffect',
|
|
38
|
-
'useDebugValue'
|
|
39
|
-
];
|
|
40
|
-
|
|
41
|
-
const customHooks = [
|
|
42
|
-
'useUnifiedAuth', 'useOrganisations', 'useEvents',
|
|
43
|
-
'usePermissions', 'useCan', 'useSecureSupabase', 'useToast',
|
|
44
|
-
'useDebounce', 'useZodForm', 'useFileReference', 'useRBAC'
|
|
45
|
-
];
|
|
46
|
-
|
|
47
|
-
// Combined list for pattern matching
|
|
48
|
-
const hookNames = [...actualReactHooks, ...customHooks];
|
|
49
|
-
|
|
50
|
-
const hookPattern = new RegExp(`\\b(${hookNames.join('|')})\\s*\\(`, 'g');
|
|
51
|
-
|
|
52
|
-
for (const filePath of files) {
|
|
53
|
-
try {
|
|
54
|
-
// Only check React component files
|
|
55
|
-
if (!filePath.match(/\.(tsx|jsx)$/)) {
|
|
56
|
-
continue;
|
|
57
|
-
}
|
|
58
|
-
|
|
59
|
-
const content = fs.readFileSync(filePath, 'utf8');
|
|
60
|
-
const relativePath = getRelativePath(filePath, projectRoot);
|
|
61
|
-
const normalizedPath = relativePath.replace(/\\/g, '/');
|
|
62
|
-
|
|
63
|
-
// Skip root-level src directory - in pace-core repository, this is a demo/showcase app
|
|
64
|
-
const isRootSrc = normalizedPath.startsWith('src/') && !normalizedPath.includes('packages/');
|
|
65
|
-
if (isRootSrc) {
|
|
66
|
-
continue; // Skip demo app files
|
|
67
|
-
}
|
|
68
|
-
|
|
69
|
-
// Skip scripts directory - utility scripts don't need hooks validation
|
|
70
|
-
const isScript = normalizedPath.startsWith('scripts/') || normalizedPath.includes('/scripts/');
|
|
71
|
-
if (isScript) {
|
|
72
|
-
continue; // Skip script files
|
|
73
|
-
}
|
|
74
|
-
|
|
75
|
-
// Check if this is a pace-core package file
|
|
76
|
-
// Skip "hook-after-return" check for pace-core files - these are false positives.
|
|
77
|
-
// The detection logic has issues with complex nested structures in library code.
|
|
78
|
-
// Other hook checks (conditional calls, missing dependencies) still apply to pace-core.
|
|
79
|
-
const isPaceCorePackage = normalizedPath.includes('packages/core/') || normalizedPath.startsWith('packages/core/');
|
|
80
|
-
|
|
81
|
-
// Check for hooks
|
|
82
|
-
if (!hookPattern.test(content)) {
|
|
83
|
-
continue; // No hooks in this file
|
|
84
|
-
}
|
|
85
|
-
|
|
86
|
-
// Find all hook calls
|
|
87
|
-
const hookCalls = [];
|
|
88
|
-
let match;
|
|
89
|
-
const regex = new RegExp(`\\b(${hookNames.join('|')})\\s*\\(`, 'g');
|
|
90
|
-
while ((match = regex.exec(content)) !== null) {
|
|
91
|
-
hookCalls.push({
|
|
92
|
-
name: match[1],
|
|
93
|
-
index: match.index,
|
|
94
|
-
line: getLineNumber(content, match.index)
|
|
95
|
-
});
|
|
96
|
-
}
|
|
97
|
-
|
|
98
|
-
if (hookCalls.length === 0) {
|
|
99
|
-
continue;
|
|
100
|
-
}
|
|
101
|
-
|
|
102
|
-
// Check for hooks called conditionally
|
|
103
|
-
for (const hookCall of hookCalls) {
|
|
104
|
-
const beforeHook = content.substring(0, hookCall.index);
|
|
105
|
-
const linesBefore = beforeHook.split('\n');
|
|
106
|
-
const currentLine = linesBefore[linesBefore.length - 1];
|
|
107
|
-
|
|
108
|
-
// Check if hook is in a conditional
|
|
109
|
-
const isInConditional = /if\s*\(|else\s*\{|switch\s*\(|case\s+.*:|for\s*\(|while\s*\(|\.map\s*\(/.test(currentLine);
|
|
110
|
-
|
|
111
|
-
if (isInConditional) {
|
|
112
|
-
issues.push({
|
|
113
|
-
type: 'hook-in-conditional',
|
|
114
|
-
file: relativePath,
|
|
115
|
-
line: hookCall.line,
|
|
116
|
-
message: `Hook '${hookCall.name}' is called conditionally or in a loop`,
|
|
117
|
-
recommendation: 'Hooks must be called at the top level of the component, not conditionally or in loops'
|
|
118
|
-
});
|
|
119
|
-
}
|
|
120
|
-
|
|
121
|
-
// Check if there's an early return before this hook at the component level
|
|
122
|
-
// Only flag returns at the component function level, not inside nested functions
|
|
123
|
-
const functionStart = beforeHook.lastIndexOf('function') > beforeHook.lastIndexOf('=>') ?
|
|
124
|
-
beforeHook.lastIndexOf('function') : beforeHook.lastIndexOf('=>');
|
|
125
|
-
|
|
126
|
-
if (functionStart !== -1) {
|
|
127
|
-
const functionBody = content.substring(functionStart, hookCall.index);
|
|
128
|
-
|
|
129
|
-
// Find all return statements and check if they're at component level (not in nested functions)
|
|
130
|
-
// Only flag CONDITIONAL early returns (guard clauses), not the final return statement
|
|
131
|
-
const returnPattern = /\breturn\s+[^;]+;|\breturn\s*;|\breturn\s+\(/g;
|
|
132
|
-
let returnMatch;
|
|
133
|
-
let hasComponentLevelReturn = false;
|
|
134
|
-
|
|
135
|
-
while ((returnMatch = returnPattern.exec(functionBody)) !== null) {
|
|
136
|
-
const returnIndex = returnMatch.index;
|
|
137
|
-
const beforeReturn = functionBody.substring(Math.max(0, returnIndex - 200), returnIndex);
|
|
138
|
-
const afterReturn = functionBody.substring(returnIndex, Math.min(functionBody.length, returnIndex + 100));
|
|
139
|
-
|
|
140
|
-
// Check if this is a CONDITIONAL early return (guard clause)
|
|
141
|
-
// Pattern: if (...) return ...; or if (...) { return ...; }
|
|
142
|
-
// NOT the final return statement of the component
|
|
143
|
-
const isConditionalReturn = /\bif\s*\([^)]+\)\s*(return|{[\s\S]*?return)/.test(beforeReturn) ||
|
|
144
|
-
/\belse\s+if\s*\([^)]+\)\s*(return|{[\s\S]*?return)/.test(beforeReturn) ||
|
|
145
|
-
/\belse\s*{\s*return/.test(beforeReturn) ||
|
|
146
|
-
/\?\s*\([^)]*\)\s*=>\s*{?\s*return/.test(beforeReturn); // Ternary operator
|
|
147
|
-
|
|
148
|
-
// If it's not a conditional return, it's likely the final return - skip it
|
|
149
|
-
if (!isConditionalReturn) {
|
|
150
|
-
continue;
|
|
151
|
-
}
|
|
152
|
-
|
|
153
|
-
// Check if return is inside a nested function by looking for patterns
|
|
154
|
-
const isInHookInitializer = /useState\s*\(\s*\([^)]*\)\s*=>/.test(beforeReturn);
|
|
155
|
-
const isInEffectCallback = /useEffect\s*\(\s*\([^)]*\)\s*=>/.test(beforeReturn);
|
|
156
|
-
const isInQueryFn = /queryFn\s*:\s*\([^)]*\)\s*=>|useQuery\s*\(\s*\{[^}]*queryFn/.test(beforeReturn);
|
|
157
|
-
const isInArrayMethod = /\.(map|filter|find|reduce|forEach|some|every)\s*\(\s*\([^)]*\)\s*=>/.test(beforeReturn);
|
|
158
|
-
const isInIIFE = /\(\s*\([^)]*\)\s*=>/.test(beforeReturn);
|
|
159
|
-
const isInFunctionExpr = /\bfunction\s*\([^)]*\)\s*\{/.test(beforeReturn);
|
|
160
|
-
const isInUseMemo = /useMemo\s*\(\s*\([^)]*\)\s*=>/.test(beforeReturn);
|
|
161
|
-
const isInUseCallback = /useCallback\s*\(\s*\([^)]*\)\s*=>/.test(beforeReturn);
|
|
162
|
-
|
|
163
|
-
// Check if return is inside a switch case (also nested)
|
|
164
|
-
const isInSwitchCase = /\bcase\s+[^:]+:\s*[\s\S]*?return/.test(beforeReturn);
|
|
165
|
-
|
|
166
|
-
// If return is NOT in any nested function pattern, it's a component-level conditional return
|
|
167
|
-
if (!isInHookInitializer && !isInEffectCallback && !isInQueryFn &&
|
|
168
|
-
!isInArrayMethod && !isInIIFE && !isInFunctionExpr &&
|
|
169
|
-
!isInUseMemo && !isInUseCallback && !isInSwitchCase) {
|
|
170
|
-
hasComponentLevelReturn = true;
|
|
171
|
-
break;
|
|
172
|
-
}
|
|
173
|
-
}
|
|
174
|
-
|
|
175
|
-
if (hasComponentLevelReturn && !isPaceCorePackage) {
|
|
176
|
-
// Skip this check for pace-core files - false positives due to complex nested structures
|
|
177
|
-
issues.push({
|
|
178
|
-
type: 'hook-after-return',
|
|
179
|
-
file: relativePath,
|
|
180
|
-
line: hookCall.line,
|
|
181
|
-
message: `Hook '${hookCall.name}' is called after an early return`,
|
|
182
|
-
recommendation: 'All hooks must be called before any conditional returns at the component level. Move hooks to the top of the component, before any component-level conditional returns. Returns inside nested functions (useState initializers, useEffect callbacks, etc.) are fine. If you have guard clauses (if (loading) return <Loading />), move all hooks above these guard clauses.'
|
|
183
|
-
});
|
|
184
|
-
}
|
|
185
|
-
}
|
|
186
|
-
}
|
|
187
|
-
|
|
188
|
-
// Check for missing dependencies in useEffect, useMemo, useCallback
|
|
189
|
-
const dependencyHooks = ['useEffect', 'useMemo', 'useCallback', 'useLayoutEffect'];
|
|
190
|
-
dependencyHooks.forEach(hookName => {
|
|
191
|
-
// Match hook with dependency array - handle multiline and various formats
|
|
192
|
-
const hookRegex = new RegExp(`${hookName}\\s*\\(([^)]*)\\)\\s*,\\s*\\[([^\\]]*)\\]`, 'gs');
|
|
193
|
-
let depMatch;
|
|
194
|
-
while ((depMatch = hookRegex.exec(content)) !== null) {
|
|
195
|
-
const hookBody = depMatch[1]; // The function body or callback
|
|
196
|
-
const deps = depMatch[2].trim();
|
|
197
|
-
|
|
198
|
-
// If dependency array is empty, check if hook only uses stable globals
|
|
199
|
-
if (deps === '') {
|
|
200
|
-
// Extract the actual callback body (handle arrow functions and regular functions)
|
|
201
|
-
let callbackBody = hookBody;
|
|
202
|
-
|
|
203
|
-
// Check if it's an arrow function: () => { ... } or () => ...
|
|
204
|
-
if (hookBody.includes('=>')) {
|
|
205
|
-
const arrowMatch = hookBody.match(/=>\s*\{?([^}]*)\}?$/);
|
|
206
|
-
if (arrowMatch) {
|
|
207
|
-
callbackBody = arrowMatch[1];
|
|
208
|
-
}
|
|
209
|
-
}
|
|
210
|
-
|
|
211
|
-
// Check if callback only uses stable global APIs or imported module-level constants
|
|
212
|
-
// Stable globals that don't need dependencies:
|
|
213
|
-
const stableGlobals = [
|
|
214
|
-
/window\.(location|document|localStorage|sessionStorage|navigator)/,
|
|
215
|
-
/document\.(documentElement|body|getElementById|querySelector)/,
|
|
216
|
-
/localStorage\.(getItem|setItem|removeItem|clear)/,
|
|
217
|
-
/sessionStorage\.(getItem|setItem|removeItem|clear)/,
|
|
218
|
-
/console\.(log|error|warn|info|debug)/,
|
|
219
|
-
/Math\./,
|
|
220
|
-
/Date\./,
|
|
221
|
-
/JSON\./,
|
|
222
|
-
/Object\./,
|
|
223
|
-
/Array\./,
|
|
224
|
-
/String\./,
|
|
225
|
-
/Number\./,
|
|
226
|
-
/Boolean\./,
|
|
227
|
-
/RegExp\./,
|
|
228
|
-
/Error\./,
|
|
229
|
-
/Promise\./,
|
|
230
|
-
/Symbol\./,
|
|
231
|
-
/Reflect\./,
|
|
232
|
-
/Proxy\./
|
|
233
|
-
];
|
|
234
|
-
|
|
235
|
-
// Check for imported module-level constants (classes, functions, constants)
|
|
236
|
-
// Pattern: ImportedClass.method() or importedFunction() or importedConstant
|
|
237
|
-
const importedModulePattern = /[A-Z]\w+\.\w+\(|^[A-Z]\w+\(|\b[A-Z][A-Z_]+/;
|
|
238
|
-
|
|
239
|
-
// Check if callback body only contains stable globals or simple operations
|
|
240
|
-
const hasOnlyStableGlobals = stableGlobals.some(pattern => pattern.test(callbackBody));
|
|
241
|
-
const hasImportedModule = importedModulePattern.test(callbackBody);
|
|
242
|
-
|
|
243
|
-
// Check if callback uses reactive values (props, state, context) that would need deps
|
|
244
|
-
const hasReactiveValues = /\b(props|state|context|use[A-Z]\w+\(\))/.test(callbackBody);
|
|
245
|
-
|
|
246
|
-
// Check for variable references that might be reactive
|
|
247
|
-
// Simple heuristic: if callback references variables that aren't stable globals
|
|
248
|
-
const variablePattern = /\b[a-z][a-zA-Z0-9_]*\b/g;
|
|
249
|
-
const variables = callbackBody.match(variablePattern) || [];
|
|
250
|
-
const hasNonGlobalVariables = variables.some(v => {
|
|
251
|
-
// Skip common stable globals and built-ins
|
|
252
|
-
const stableVars = ['window', 'document', 'localStorage', 'sessionStorage', 'console',
|
|
253
|
-
'Math', 'Date', 'JSON', 'Object', 'Array', 'String', 'Number',
|
|
254
|
-
'Boolean', 'RegExp', 'Error', 'Promise', 'Symbol', 'Reflect', 'Proxy',
|
|
255
|
-
'true', 'false', 'null', 'undefined', 'this', 'return', 'if', 'else',
|
|
256
|
-
'const', 'let', 'var', 'function', 'async', 'await', 'for', 'while',
|
|
257
|
-
'switch', 'case', 'default', 'break', 'continue', 'try', 'catch', 'finally'];
|
|
258
|
-
return !stableVars.includes(v) && !v.match(/^[A-Z]/); // Lowercase vars that aren't stable
|
|
259
|
-
});
|
|
260
|
-
|
|
261
|
-
// Only warn if callback uses reactive values or non-global variables
|
|
262
|
-
if (hasReactiveValues || (hasNonGlobalVariables && !hasOnlyStableGlobals && !hasImportedModule)) {
|
|
263
|
-
warnings.push({
|
|
264
|
-
type: 'missing-dependencies',
|
|
265
|
-
file: relativePath,
|
|
266
|
-
line: getLineNumber(content, depMatch.index),
|
|
267
|
-
message: `${hookName} has empty dependency array but may need dependencies`,
|
|
268
|
-
recommendation: 'Review the hook body and add all dependencies to the dependency array. If the callback only uses stable global APIs (window, document, localStorage, etc.) or imported module-level constants, an empty dependency array is correct.'
|
|
269
|
-
});
|
|
270
|
-
}
|
|
271
|
-
}
|
|
272
|
-
}
|
|
273
|
-
});
|
|
274
|
-
|
|
275
|
-
// Check hook order (hooks should be called in consistent order)
|
|
276
|
-
// This is a simplified check - full implementation would track hook order across renders
|
|
277
|
-
|
|
278
|
-
// SKIP hook grouping suggestions for pace-core library files
|
|
279
|
-
// Library components often have complex hook usage organized by feature/concern,
|
|
280
|
-
// and strict grouping is less critical than in consuming applications.
|
|
281
|
-
// The grouping suggestions are primarily for consuming apps, not library code.
|
|
282
|
-
if (isPaceCorePackage) {
|
|
283
|
-
continue; // Skip grouping suggestions for pace-core library files
|
|
284
|
-
}
|
|
285
|
-
|
|
286
|
-
// Filter to only actual React hooks (not custom hooks) for grouping suggestions
|
|
287
|
-
// Files with only custom hooks (like useToast()) don't need grouping suggestions
|
|
288
|
-
const actualReactHookCalls = hookCalls.filter(h => actualReactHooks.includes(h.name));
|
|
289
|
-
|
|
290
|
-
// Skip files that only have custom hooks (no actual React hooks)
|
|
291
|
-
// Custom hooks are typically called once and don't need grouping
|
|
292
|
-
// This also filters out files like Toast.tsx that only have forwardRef components
|
|
293
|
-
// (forwardRef components don't use React hooks, so actualReactHookCalls will be empty)
|
|
294
|
-
if (actualReactHookCalls.length === 0) {
|
|
295
|
-
continue; // No actual React hooks, skip grouping suggestion
|
|
296
|
-
}
|
|
297
|
-
|
|
298
|
-
// Check if hooks are already grouped (look for grouping comments)
|
|
299
|
-
// Accept more flexible patterns:
|
|
300
|
-
// - Specific keywords: // ============================================================================ // REFS / STATE HOOKS / etc.
|
|
301
|
-
// - Section dividers: // ============================================================================ (any text)
|
|
302
|
-
// - Comment blocks near hooks: // HOOKS or // State management or similar
|
|
303
|
-
const hasGroupingComments =
|
|
304
|
-
/\/\/\s*=+\s*(REFS|STATE HOOKS|CUSTOM HOOKS|MEMOIZATION HOOKS|EFFECT HOOKS|HOOKS|STATE|EFFECTS|MEMOIZATION|CALLBACKS)/i.test(content) ||
|
|
305
|
-
/\/\/\s*=+\s*[A-Z].*HOOK/i.test(content) ||
|
|
306
|
-
/\/\/\s*=+\s*[A-Z].*STATE/i.test(content) ||
|
|
307
|
-
/\/\/\s*=+\s*[A-Z].*EFFECT/i.test(content);
|
|
308
|
-
|
|
309
|
-
// Only suggest grouping if:
|
|
310
|
-
// 1. File has actual React hooks (not just custom hooks)
|
|
311
|
-
// 2. Hooks are called multiple times (duplicate hook names)
|
|
312
|
-
// 3. Hooks are not already grouped (no grouping comments found)
|
|
313
|
-
const hookOrder = actualReactHookCalls.map(h => h.name);
|
|
314
|
-
const uniqueHooks = [...new Set(hookOrder)];
|
|
315
|
-
|
|
316
|
-
if (uniqueHooks.length !== hookOrder.length && !hasGroupingComments) {
|
|
317
|
-
// Hooks are called multiple times and not grouped
|
|
318
|
-
suggestions.push({
|
|
319
|
-
type: 'hook-order',
|
|
320
|
-
file: relativePath,
|
|
321
|
-
message: 'Consider grouping related hooks together for better readability',
|
|
322
|
-
recommendation: 'Group hooks by purpose (state hooks, effect hooks, custom hooks)'
|
|
323
|
-
});
|
|
324
|
-
}
|
|
325
|
-
} catch (error) {
|
|
326
|
-
// Skip files with errors
|
|
327
|
-
}
|
|
328
|
-
}
|
|
329
|
-
|
|
330
|
-
return { issues, warnings, suggestions };
|
|
331
|
-
}
|
|
332
|
-
};
|
|
333
|
-
|
|
334
|
-
module.exports = hooksCheck;
|
|
@@ -1,244 +0,0 @@
|
|
|
1
|
-
#!/usr/bin/env node
|
|
2
|
-
|
|
3
|
-
/**
|
|
4
|
-
* Import Patterns Check Module
|
|
5
|
-
* @package @jmruthers/pace-core
|
|
6
|
-
* @module Audit/Checks/Imports
|
|
7
|
-
*
|
|
8
|
-
* Checks for:
|
|
9
|
-
* - Unused imports from pace-core
|
|
10
|
-
* - Missing default imports
|
|
11
|
-
* - Circular dependencies
|
|
12
|
-
* - Wrong import paths
|
|
13
|
-
* - Barrel import anti-patterns
|
|
14
|
-
*/
|
|
15
|
-
|
|
16
|
-
const fs = require('fs');
|
|
17
|
-
const path = require('path');
|
|
18
|
-
const { findSourceFiles, getRelativePath } = require('../utils.cjs');
|
|
19
|
-
|
|
20
|
-
const importsCheck = {
|
|
21
|
-
name: 'imports',
|
|
22
|
-
description: 'Import pattern analysis (unused imports, circular dependencies, wrong paths)',
|
|
23
|
-
severity: 'warning',
|
|
24
|
-
|
|
25
|
-
async run(context) {
|
|
26
|
-
const { projectRoot, files } = context;
|
|
27
|
-
const issues = [];
|
|
28
|
-
const warnings = [];
|
|
29
|
-
const suggestions = [];
|
|
30
|
-
|
|
31
|
-
if (!files || files.length === 0) {
|
|
32
|
-
return { issues, warnings, suggestions };
|
|
33
|
-
}
|
|
34
|
-
|
|
35
|
-
// Track imports and usage
|
|
36
|
-
const importMap = new Map(); // file -> { imports: [], exports: [] }
|
|
37
|
-
const usageMap = new Map(); // file -> Set of used identifiers
|
|
38
|
-
|
|
39
|
-
// First pass: collect imports and exports
|
|
40
|
-
for (const filePath of files) {
|
|
41
|
-
try {
|
|
42
|
-
const content = fs.readFileSync(filePath, 'utf8');
|
|
43
|
-
const relativePath = getRelativePath(filePath, projectRoot);
|
|
44
|
-
|
|
45
|
-
const imports = [];
|
|
46
|
-
const exports = [];
|
|
47
|
-
const used = new Set();
|
|
48
|
-
|
|
49
|
-
// Find all imports
|
|
50
|
-
const importPattern = /import\s+(?:(?:\*\s+as\s+(\w+))|(?:{([^}]+)})|(\w+))\s+from\s+['"]([^'"]+)['"]/g;
|
|
51
|
-
let match;
|
|
52
|
-
while ((match = importPattern.exec(content)) !== null) {
|
|
53
|
-
const namespace = match[1];
|
|
54
|
-
const namedImports = match[2];
|
|
55
|
-
const defaultImport = match[3];
|
|
56
|
-
const modulePath = match[4];
|
|
57
|
-
|
|
58
|
-
imports.push({
|
|
59
|
-
namespace,
|
|
60
|
-
namedImports: namedImports ? namedImports.split(',').map(s => s.trim().replace(/\s+as\s+\w+/, '')) : [],
|
|
61
|
-
defaultImport,
|
|
62
|
-
modulePath,
|
|
63
|
-
line: content.substring(0, match.index).split('\n').length
|
|
64
|
-
});
|
|
65
|
-
}
|
|
66
|
-
|
|
67
|
-
// Find all exports
|
|
68
|
-
const exportPattern = /export\s+(?:(?:default\s+)?(?:function|const|class|interface|type)\s+(\w+)|(?:{([^}]+)})|(\w+))/g;
|
|
69
|
-
while ((match = exportPattern.exec(content)) !== null) {
|
|
70
|
-
const namedExport = match[1] || match[3];
|
|
71
|
-
const namedExports = match[2];
|
|
72
|
-
|
|
73
|
-
if (namedExport) {
|
|
74
|
-
exports.push(namedExport);
|
|
75
|
-
}
|
|
76
|
-
if (namedExports) {
|
|
77
|
-
exports.push(...namedExports.split(',').map(s => s.trim()));
|
|
78
|
-
}
|
|
79
|
-
}
|
|
80
|
-
|
|
81
|
-
// Find usage of imported identifiers (simplified heuristic)
|
|
82
|
-
imports.forEach(imp => {
|
|
83
|
-
if (imp.defaultImport) {
|
|
84
|
-
// Check if default import is used
|
|
85
|
-
const usagePattern = new RegExp(`\\b${imp.defaultImport}\\b`, 'g');
|
|
86
|
-
if (usagePattern.test(content)) {
|
|
87
|
-
used.add(imp.defaultImport);
|
|
88
|
-
}
|
|
89
|
-
}
|
|
90
|
-
if (imp.namespace) {
|
|
91
|
-
const usagePattern = new RegExp(`\\b${imp.namespace}\\.`, 'g');
|
|
92
|
-
if (usagePattern.test(content)) {
|
|
93
|
-
used.add(imp.namespace);
|
|
94
|
-
}
|
|
95
|
-
}
|
|
96
|
-
imp.namedImports.forEach(name => {
|
|
97
|
-
const cleanName = name.trim();
|
|
98
|
-
const usagePattern = new RegExp(`\\b${cleanName}\\b`, 'g');
|
|
99
|
-
// Count occurrences - if more than 1, it's likely used (1 is the import itself)
|
|
100
|
-
const matches = content.match(usagePattern);
|
|
101
|
-
if (matches && matches.length > 1) {
|
|
102
|
-
used.add(cleanName);
|
|
103
|
-
}
|
|
104
|
-
});
|
|
105
|
-
});
|
|
106
|
-
|
|
107
|
-
importMap.set(relativePath, { imports, exports, content });
|
|
108
|
-
usageMap.set(relativePath, used);
|
|
109
|
-
} catch (error) {
|
|
110
|
-
// Skip files with errors
|
|
111
|
-
}
|
|
112
|
-
}
|
|
113
|
-
|
|
114
|
-
// Second pass: analyze imports
|
|
115
|
-
for (const [filePath, { imports, content }] of importMap.entries()) {
|
|
116
|
-
const relativePath = filePath;
|
|
117
|
-
const normalizedPath = relativePath.replace(/\\/g, '/');
|
|
118
|
-
|
|
119
|
-
// Skip pace-core package files - imports check is for consuming applications, not the library itself
|
|
120
|
-
// The library must use internal import paths and may have unused imports in examples/config files
|
|
121
|
-
const isPaceCorePackage = normalizedPath.includes('packages/core/') || normalizedPath.startsWith('packages/core/');
|
|
122
|
-
if (isPaceCorePackage) {
|
|
123
|
-
continue; // Skip library files
|
|
124
|
-
}
|
|
125
|
-
|
|
126
|
-
// Skip root-level src directory - in pace-core repository, this is a demo/showcase app
|
|
127
|
-
const isRootSrc = normalizedPath.startsWith('src/') && !normalizedPath.includes('packages/');
|
|
128
|
-
if (isRootSrc) {
|
|
129
|
-
continue; // Skip demo app files
|
|
130
|
-
}
|
|
131
|
-
|
|
132
|
-
// Skip scripts directory - utility scripts don't need import validation
|
|
133
|
-
const isScript = normalizedPath.startsWith('scripts/') || normalizedPath.includes('/scripts/');
|
|
134
|
-
if (isScript) {
|
|
135
|
-
continue; // Skip script files
|
|
136
|
-
}
|
|
137
|
-
|
|
138
|
-
const used = usageMap.get(filePath) || new Set();
|
|
139
|
-
|
|
140
|
-
imports.forEach(imp => {
|
|
141
|
-
// Check for unused imports from pace-core
|
|
142
|
-
if (imp.modulePath === '@jmruthers/pace-core' || imp.modulePath.startsWith('@jmruthers/pace-core/')) {
|
|
143
|
-
const unused = [];
|
|
144
|
-
|
|
145
|
-
if (imp.defaultImport && !used.has(imp.defaultImport)) {
|
|
146
|
-
unused.push(imp.defaultImport);
|
|
147
|
-
}
|
|
148
|
-
|
|
149
|
-
imp.namedImports.forEach(name => {
|
|
150
|
-
if (!used.has(name.trim())) {
|
|
151
|
-
unused.push(name.trim());
|
|
152
|
-
}
|
|
153
|
-
});
|
|
154
|
-
|
|
155
|
-
if (imp.namespace && !used.has(imp.namespace)) {
|
|
156
|
-
unused.push(`${imp.namespace} (namespace)`);
|
|
157
|
-
}
|
|
158
|
-
|
|
159
|
-
if (unused.length > 0) {
|
|
160
|
-
warnings.push({
|
|
161
|
-
type: 'unused-import',
|
|
162
|
-
file: relativePath,
|
|
163
|
-
line: imp.line,
|
|
164
|
-
message: `Unused imports from pace-core: ${unused.join(', ')}`,
|
|
165
|
-
recommendation: `Remove unused imports to reduce bundle size`
|
|
166
|
-
});
|
|
167
|
-
}
|
|
168
|
-
}
|
|
169
|
-
|
|
170
|
-
// Check for wrong import paths
|
|
171
|
-
if (imp.modulePath.startsWith('@jmruthers/pace-core/')) {
|
|
172
|
-
const validPaths = [
|
|
173
|
-
'@jmruthers/pace-core',
|
|
174
|
-
'@jmruthers/pace-core/components',
|
|
175
|
-
'@jmruthers/pace-core/hooks',
|
|
176
|
-
'@jmruthers/pace-core/utils',
|
|
177
|
-
'@jmruthers/pace-core/providers',
|
|
178
|
-
'@jmruthers/pace-core/rbac',
|
|
179
|
-
'@jmruthers/pace-core/types'
|
|
180
|
-
];
|
|
181
|
-
|
|
182
|
-
if (!validPaths.includes(imp.modulePath)) {
|
|
183
|
-
warnings.push({
|
|
184
|
-
type: 'wrong-import-path',
|
|
185
|
-
file: relativePath,
|
|
186
|
-
line: imp.line,
|
|
187
|
-
message: `Invalid import path: ${imp.modulePath}`,
|
|
188
|
-
recommendation: `Use one of the valid paths: ${validPaths.join(', ')}`
|
|
189
|
-
});
|
|
190
|
-
}
|
|
191
|
-
}
|
|
192
|
-
|
|
193
|
-
// Check for barrel import anti-patterns (importing entire modules)
|
|
194
|
-
if (imp.namespace && (imp.modulePath === '@jmruthers/pace-core' || imp.modulePath.startsWith('@jmruthers/pace-core/'))) {
|
|
195
|
-
warnings.push({
|
|
196
|
-
type: 'barrel-import',
|
|
197
|
-
file: relativePath,
|
|
198
|
-
line: imp.line,
|
|
199
|
-
message: `Namespace import from pace-core: import * as ${imp.namespace}`,
|
|
200
|
-
recommendation: `Import specific exports instead to enable tree-shaking: import { Component1, Component2 } from '@jmruthers/pace-core'`
|
|
201
|
-
});
|
|
202
|
-
}
|
|
203
|
-
});
|
|
204
|
-
}
|
|
205
|
-
|
|
206
|
-
// Check for circular dependencies (simplified - check if file A imports B and B imports A)
|
|
207
|
-
const filePaths = Array.from(importMap.keys());
|
|
208
|
-
for (let i = 0; i < filePaths.length; i++) {
|
|
209
|
-
for (let j = i + 1; j < filePaths.length; j++) {
|
|
210
|
-
const fileA = filePaths[i];
|
|
211
|
-
const fileB = filePaths[j];
|
|
212
|
-
const importsA = importMap.get(fileA)?.imports || [];
|
|
213
|
-
const importsB = importMap.get(fileB)?.imports || [];
|
|
214
|
-
|
|
215
|
-
// Check if A imports B
|
|
216
|
-
const aImportsB = importsA.some(imp => {
|
|
217
|
-
const importPath = imp.modulePath;
|
|
218
|
-
return importPath.startsWith('./') || importPath.startsWith('../') ?
|
|
219
|
-
path.resolve(path.dirname(fileA), importPath) === fileB : false;
|
|
220
|
-
});
|
|
221
|
-
|
|
222
|
-
// Check if B imports A
|
|
223
|
-
const bImportsA = importsB.some(imp => {
|
|
224
|
-
const importPath = imp.modulePath;
|
|
225
|
-
return importPath.startsWith('./') || importPath.startsWith('../') ?
|
|
226
|
-
path.resolve(path.dirname(fileB), importPath) === fileA : false;
|
|
227
|
-
});
|
|
228
|
-
|
|
229
|
-
if (aImportsB && bImportsA) {
|
|
230
|
-
issues.push({
|
|
231
|
-
type: 'circular-dependency',
|
|
232
|
-
file: fileA,
|
|
233
|
-
message: `Circular dependency detected between ${fileA} and ${fileB}`,
|
|
234
|
-
recommendation: `Refactor to break the circular dependency by extracting shared code to a third module`
|
|
235
|
-
});
|
|
236
|
-
}
|
|
237
|
-
}
|
|
238
|
-
}
|
|
239
|
-
|
|
240
|
-
return { issues, warnings, suggestions };
|
|
241
|
-
}
|
|
242
|
-
};
|
|
243
|
-
|
|
244
|
-
module.exports = importsCheck;
|