@jmruthers/pace-core 0.6.2 → 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 +45 -0
- package/cursor-rules/00-pace-core-compliance.mdc +34 -2
- package/dist/{AuthService-BPvc3Ka0.d.ts → AuthService-Cb34EQs3.d.ts} +9 -1
- package/dist/{DataTable-TPTKCX4D.js → DataTable-THFPBKTP.js} +9 -8
- package/dist/{PublicPageProvider-DC6kCaqf.d.ts → PublicPageProvider-DEMpysFR.d.ts} +45 -67
- package/dist/{UnifiedAuthProvider-CVcTjx-d.d.ts → UnifiedAuthProvider-CKvHP1MK.d.ts} +1 -8
- package/dist/{UnifiedAuthProvider-CH6Z342H.js → UnifiedAuthProvider-KAGUYQ4J.js} +5 -4
- package/dist/{api-MVVQZLJI.js → api-IAGWF3ZG.js} +10 -10
- package/dist/{audit-B5P6FFIR.js → audit-V53FV5AG.js} +2 -2
- package/dist/{chunk-SFZUDBL5.js → chunk-2T2IG7T7.js} +70 -56
- package/dist/chunk-2T2IG7T7.js.map +1 -0
- package/dist/{chunk-MMZ7JXPU.js → chunk-6Z7LTB3D.js} +13 -21
- package/dist/{chunk-MMZ7JXPU.js.map → chunk-6Z7LTB3D.js.map} +1 -1
- package/dist/{chunk-6J4GEEJR.js → chunk-CNCQDFLN.js} +53 -27
- package/dist/chunk-CNCQDFLN.js.map +1 -0
- package/dist/chunk-DGUM43GV.js +11 -0
- package/dist/{chunk-EHMR7VYL.js → chunk-DWUBLJJM.js} +361 -187
- package/dist/chunk-DWUBLJJM.js.map +1 -0
- package/dist/{chunk-2UOI2FG5.js → chunk-HFZBI76P.js} +4 -4
- package/dist/{chunk-F2IMUDXZ.js → chunk-M7MPQISP.js} +2 -2
- package/dist/{chunk-3XC4CPTD.js → chunk-PQBSKX33.js} +244 -5727
- 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-24UVZUZG.js → chunk-RWEBCB47.js} +129 -387
- package/dist/chunk-RWEBCB47.js.map +1 -0
- package/dist/{chunk-XWQCNGTQ.js → chunk-YDQHOZNA.js} +173 -79
- package/dist/chunk-YDQHOZNA.js.map +1 -0
- package/dist/{chunk-NECFR5MM.js → chunk-ZNIWI3UC.js} +562 -644
- package/dist/chunk-ZNIWI3UC.js.map +1 -0
- package/dist/components.d.ts +2 -2
- package/dist/components.js +12 -13
- 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/hooks.d.ts +2 -2
- package/dist/hooks.js +7 -6
- package/dist/hooks.js.map +1 -1
- package/dist/index.d.ts +7 -7
- package/dist/index.js +21 -16
- package/dist/index.js.map +1 -1
- package/dist/providers.d.ts +3 -3
- package/dist/providers.js +4 -3
- package/dist/rbac/index.d.ts +67 -27
- package/dist/rbac/index.js +15 -8
- package/dist/styles/index.js +1 -1
- package/dist/theming/runtime.js +1 -1
- package/dist/types.js +1 -1
- package/dist/{usePublicRouteParams-1oMokgLF.d.ts → usePublicRouteParams-i3qtoBgg.d.ts} +7 -16
- package/dist/utils.js +5 -7
- package/dist/utils.js.map +1 -1
- package/docs/api/README.md +14 -16
- package/docs/api/modules.md +3796 -2513
- package/docs/components/context-selector.md +126 -0
- package/docs/migration/RBAC_SCOPE_MIGRATION.md +385 -0
- package/docs/pace-mint-fix-auto-selection.md +218 -0
- package/docs/pace-mint-rbac-setup.md +391 -0
- package/docs/rbac/secure-client-protection.md +330 -0
- package/package.json +3 -3
- package/scripts/audit/core/checks/compliance.cjs +72 -0
- package/scripts/audit/core/checks/dependencies.cjs +559 -28
- package/scripts/audit/core/checks/documentation.cjs +68 -3
- package/scripts/audit/core/checks/environment.cjs +2 -14
- package/scripts/audit/core/checks/error-handling.cjs +47 -6
- package/src/components/ContextSelector/ContextSelector.tsx +384 -0
- package/src/components/ContextSelector/index.ts +3 -0
- package/src/components/DataTable/components/RowComponent.tsx +19 -19
- package/src/components/DataTable/components/UnifiedTableBody.tsx +2 -2
- package/src/components/DataTable/hooks/useDataTablePermissions.ts +8 -6
- package/src/components/Dialog/Dialog.tsx +29 -1
- package/src/components/FileDisplay/FileDisplay.tsx +42 -10
- package/src/components/Header/Header.test.tsx +43 -73
- package/src/components/Header/Header.tsx +44 -45
- 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 +9 -9
- package/src/components/PaceAppLayout/PaceAppLayout.tsx +135 -33
- package/src/components/PaceAppLayout/README.md +14 -17
- package/src/components/PaceAppLayout/test-setup.tsx +2 -2
- package/src/components/index.ts +5 -5
- package/src/eslint-rules/pace-core-compliance.cjs +106 -0
- package/src/hooks/__tests__/useAppConfig.unit.test.ts +4 -98
- package/src/hooks/useAppConfig.ts +15 -30
- package/src/hooks/useFileDisplay.ts +77 -50
- package/src/index.ts +4 -5
- package/src/providers/services/AuthServiceProvider.tsx +17 -7
- package/src/providers/services/EventServiceProvider.tsx +33 -5
- package/src/providers/services/UnifiedAuthProvider.tsx +90 -134
- package/src/rbac/__tests__/adapters.comprehensive.test.tsx +1 -1
- package/src/rbac/adapters.tsx +2 -2
- package/src/rbac/api.test.ts +59 -51
- package/src/rbac/api.ts +178 -132
- package/src/rbac/components/PagePermissionGuard.tsx +38 -10
- package/src/rbac/hooks/__tests__/useSecureSupabase.test.ts +32 -21
- package/src/rbac/hooks/permissions/useAccessLevel.ts +1 -1
- package/src/rbac/hooks/permissions/useCan.ts +41 -11
- package/src/rbac/hooks/permissions/useHasAllPermissions.ts +1 -1
- package/src/rbac/hooks/permissions/useHasAnyPermission.ts +1 -1
- package/src/rbac/hooks/permissions/useMultiplePermissions.ts +1 -1
- package/src/rbac/hooks/useCan.test.ts +0 -9
- 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/useSecureSupabase.ts +7 -7
- package/src/rbac/index.ts +7 -0
- package/src/rbac/secureClient.test.ts +22 -18
- package/src/rbac/secureClient.ts +103 -16
- package/src/rbac/security.ts +0 -17
- package/src/rbac/types.ts +1 -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/utils/dynamic/dynamicUtils.ts +7 -4
- package/dist/chunk-24UVZUZG.js.map +0 -1
- package/dist/chunk-3XC4CPTD.js.map +0 -1
- package/dist/chunk-6J4GEEJR.js.map +0 -1
- package/dist/chunk-7D4SUZUM.js +0 -38
- package/dist/chunk-EHMR7VYL.js.map +0 -1
- package/dist/chunk-NECFR5MM.js.map +0 -1
- package/dist/chunk-SFZUDBL5.js.map +0 -1
- package/dist/chunk-XWQCNGTQ.js.map +0 -1
- package/docs/api/classes/ColumnFactory.md +0 -243
- 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 -163
- 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 -34
- package/docs/api/interfaces/ButtonProps.md +0 -56
- package/docs/api/interfaces/CalendarProps.md +0 -73
- package/docs/api/interfaces/CardProps.md +0 -69
- 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 -252
- 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/ErrorBoundaryProps.md +0 -147
- package/docs/api/interfaces/ErrorBoundaryProviderProps.md +0 -36
- package/docs/api/interfaces/ErrorBoundaryState.md +0 -75
- 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 -296
- package/docs/api/interfaces/FooterProps.md +0 -107
- 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 -56
- package/docs/api/interfaces/LabelProps.md +0 -107
- package/docs/api/interfaces/LoggerConfig.md +0 -62
- package/docs/api/interfaces/LoginFormProps.md +0 -187
- 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 -409
- package/docs/api/interfaces/PaceLoginPageProps.md +0 -49
- 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 -78
- package/docs/api/interfaces/PublicPageFooterProps.md +0 -112
- package/docs/api/interfaces/PublicPageHeaderProps.md +0 -125
- package/docs/api/interfaces/PublicPageLayoutProps.md +0 -185
- 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 -12
- package/docs/api/interfaces/ToastProps.md +0 -9
- package/docs/api/interfaces/UnifiedAuthContextType.md +0 -823
- package/docs/api/interfaces/UnifiedAuthProviderProps.md +0 -173
- package/docs/api/interfaces/UseFormDialogOptions.md +0 -62
- package/docs/api/interfaces/UseFormDialogReturn.md +0 -117
- package/docs/api/interfaces/UseInactivityTrackerOptions.md +0 -138
- package/docs/api/interfaces/UseInactivityTrackerReturn.md +0 -123
- package/docs/api/interfaces/UsePublicEventLogoOptions.md +0 -87
- package/docs/api/interfaces/UsePublicEventLogoReturn.md +0 -84
- package/docs/api/interfaces/UsePublicEventOptions.md +0 -34
- package/docs/api/interfaces/UsePublicEventReturn.md +0 -71
- package/docs/api/interfaces/UsePublicFileDisplayOptions.md +0 -47
- package/docs/api/interfaces/UsePublicFileDisplayReturn.md +0 -123
- package/docs/api/interfaces/UsePublicRouteParamsReturn.md +0 -97
- 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 -121
- package/docs/api/interfaces/UserMenuProps.md +0 -88
- package/docs/api/interfaces/UserProfile.md +0 -63
- package/src/components/EventSelector/EventSelector.test.tsx +0 -720
- package/src/components/EventSelector/EventSelector.tsx +0 -423
- package/src/components/EventSelector/index.ts +0 -3
- package/src/components/OrganisationSelector/OrganisationSelector.test.tsx +0 -784
- package/src/components/OrganisationSelector/OrganisationSelector.tsx +0 -327
- package/src/components/OrganisationSelector/index.ts +0 -9
- /package/dist/{DataTable-TPTKCX4D.js.map → DataTable-THFPBKTP.js.map} +0 -0
- /package/dist/{UnifiedAuthProvider-CH6Z342H.js.map → UnifiedAuthProvider-KAGUYQ4J.js.map} +0 -0
- /package/dist/{api-MVVQZLJI.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/dist/{chunk-2UOI2FG5.js.map → chunk-HFZBI76P.js.map} +0 -0
- /package/dist/{chunk-F2IMUDXZ.js.map → chunk-M7MPQISP.js.map} +0 -0
|
@@ -11,6 +11,11 @@
|
|
|
11
11
|
* - Unused dependencies
|
|
12
12
|
* - Missing peer dependencies
|
|
13
13
|
* - Version mismatches
|
|
14
|
+
* - Redundant dependencies (duplicates of pace-core deps)
|
|
15
|
+
* - Missing dependencies (used but not declared)
|
|
16
|
+
* - Misclassified dependencies (wrong section)
|
|
17
|
+
* - Version mismatches across workspace
|
|
18
|
+
* - Optional dependencies build configuration
|
|
14
19
|
*/
|
|
15
20
|
|
|
16
21
|
const fs = require('fs');
|
|
@@ -87,6 +92,59 @@ function getPaceCorePeerDeps(projectRoot) {
|
|
|
87
92
|
return {};
|
|
88
93
|
}
|
|
89
94
|
|
|
95
|
+
/**
|
|
96
|
+
* Get pace-core dependencies (runtime dependencies provided by pace-core)
|
|
97
|
+
*/
|
|
98
|
+
function getPaceCoreDeps(projectRoot) {
|
|
99
|
+
try {
|
|
100
|
+
// Try to find pace-core in node_modules
|
|
101
|
+
const paceCorePath = path.join(projectRoot, 'node_modules', '@jmruthers', 'pace-core', 'package.json');
|
|
102
|
+
if (fs.existsSync(paceCorePath)) {
|
|
103
|
+
const paceCorePkg = JSON.parse(fs.readFileSync(paceCorePath, 'utf8'));
|
|
104
|
+
return paceCorePkg.dependencies || {};
|
|
105
|
+
}
|
|
106
|
+
} catch (error) {
|
|
107
|
+
// If we can't find it, return empty object
|
|
108
|
+
}
|
|
109
|
+
return {};
|
|
110
|
+
}
|
|
111
|
+
|
|
112
|
+
/**
|
|
113
|
+
* Get pace-core package.json (for workspace checks)
|
|
114
|
+
*/
|
|
115
|
+
function getPaceCorePackageJson(projectRoot) {
|
|
116
|
+
try {
|
|
117
|
+
// Try to find pace-core in node_modules
|
|
118
|
+
const paceCorePath = path.join(projectRoot, 'node_modules', '@jmruthers', 'pace-core', 'package.json');
|
|
119
|
+
if (fs.existsSync(paceCorePath)) {
|
|
120
|
+
return JSON.parse(fs.readFileSync(paceCorePath, 'utf8'));
|
|
121
|
+
}
|
|
122
|
+
// Also try packages/core if we're in the workspace
|
|
123
|
+
const packagesCorePath = path.join(projectRoot, 'packages', 'core', 'package.json');
|
|
124
|
+
if (fs.existsSync(packagesCorePath)) {
|
|
125
|
+
return JSON.parse(fs.readFileSync(packagesCorePath, 'utf8'));
|
|
126
|
+
}
|
|
127
|
+
} catch (error) {
|
|
128
|
+
// If we can't find it, return null
|
|
129
|
+
}
|
|
130
|
+
return null;
|
|
131
|
+
}
|
|
132
|
+
|
|
133
|
+
/**
|
|
134
|
+
* Get workspace root package.json (for monorepo checks)
|
|
135
|
+
*/
|
|
136
|
+
function getWorkspaceRootPackageJson(projectRoot) {
|
|
137
|
+
try {
|
|
138
|
+
const rootPath = path.join(projectRoot, 'package.json');
|
|
139
|
+
if (fs.existsSync(rootPath)) {
|
|
140
|
+
return JSON.parse(fs.readFileSync(rootPath, 'utf8'));
|
|
141
|
+
}
|
|
142
|
+
} catch (error) {
|
|
143
|
+
// If we can't find it, return null
|
|
144
|
+
}
|
|
145
|
+
return null;
|
|
146
|
+
}
|
|
147
|
+
|
|
90
148
|
/**
|
|
91
149
|
* Check if a dependency is used in config files
|
|
92
150
|
*/
|
|
@@ -180,9 +238,99 @@ function checkNonJsFiles(projectRoot, depName) {
|
|
|
180
238
|
return false;
|
|
181
239
|
}
|
|
182
240
|
|
|
241
|
+
/**
|
|
242
|
+
* Find all test files in the project
|
|
243
|
+
*/
|
|
244
|
+
function findTestFiles(projectRoot) {
|
|
245
|
+
const testFiles = [];
|
|
246
|
+
const testPattern = /\.(test|spec)\.(ts|tsx|js|jsx)$/;
|
|
247
|
+
const ignoreDirs = ['node_modules', 'dist', 'build', '.next', 'coverage'];
|
|
248
|
+
|
|
249
|
+
const walkDir = (dir) => {
|
|
250
|
+
try {
|
|
251
|
+
const items = fs.readdirSync(dir);
|
|
252
|
+
items.forEach(item => {
|
|
253
|
+
const fullPath = path.join(dir, item);
|
|
254
|
+
const stat = fs.statSync(fullPath);
|
|
255
|
+
|
|
256
|
+
if (stat.isDirectory()) {
|
|
257
|
+
if (!ignoreDirs.includes(item) && !item.startsWith('.')) {
|
|
258
|
+
walkDir(fullPath);
|
|
259
|
+
}
|
|
260
|
+
} else if (stat.isFile() && testPattern.test(item)) {
|
|
261
|
+
testFiles.push(fullPath);
|
|
262
|
+
}
|
|
263
|
+
});
|
|
264
|
+
} catch (error) {
|
|
265
|
+
// Skip directories we can't read
|
|
266
|
+
}
|
|
267
|
+
};
|
|
268
|
+
|
|
269
|
+
try {
|
|
270
|
+
const srcPath = path.join(projectRoot, 'src');
|
|
271
|
+
if (fs.existsSync(srcPath)) {
|
|
272
|
+
walkDir(srcPath);
|
|
273
|
+
}
|
|
274
|
+
} catch (error) {
|
|
275
|
+
// Skip if can't read
|
|
276
|
+
}
|
|
277
|
+
|
|
278
|
+
return testFiles;
|
|
279
|
+
}
|
|
280
|
+
|
|
281
|
+
/**
|
|
282
|
+
* Check if a dependency is used in package.json scripts
|
|
283
|
+
*/
|
|
284
|
+
function checkPackageScripts(packageJson, depName) {
|
|
285
|
+
if (!packageJson || !packageJson.scripts) {
|
|
286
|
+
return false;
|
|
287
|
+
}
|
|
288
|
+
|
|
289
|
+
const scripts = packageJson.scripts;
|
|
290
|
+
const scriptContent = Object.values(scripts).join(' ');
|
|
291
|
+
|
|
292
|
+
// Check if the package name appears in any script
|
|
293
|
+
// This is a simple check - package names in scripts are usually the command name
|
|
294
|
+
const depNameEscaped = depName.replace(/[.*+?^${}()|[\]\\]/g, '\\$&');
|
|
295
|
+
const scriptPattern = new RegExp(`\\b${depNameEscaped}\\b`, 'i');
|
|
296
|
+
|
|
297
|
+
return scriptPattern.test(scriptContent);
|
|
298
|
+
}
|
|
299
|
+
|
|
300
|
+
/**
|
|
301
|
+
* Check if a dependency is only used in test files
|
|
302
|
+
*/
|
|
303
|
+
function isOnlyUsedInTests(projectRoot, depName, testFiles) {
|
|
304
|
+
if (testFiles.length === 0) {
|
|
305
|
+
return false;
|
|
306
|
+
}
|
|
307
|
+
|
|
308
|
+
// Check if it's used in any test file
|
|
309
|
+
let foundInTests = false;
|
|
310
|
+
for (const testFile of testFiles) {
|
|
311
|
+
try {
|
|
312
|
+
const content = fs.readFileSync(testFile, 'utf8');
|
|
313
|
+
const depNameEscaped = depName.replace(/[.*+?^${}()|[\]\\]/g, '\\$&');
|
|
314
|
+
|
|
315
|
+
// Check for imports
|
|
316
|
+
const importPattern = new RegExp(`from\\s+['"]${depNameEscaped}`, 'g');
|
|
317
|
+
const requirePattern = new RegExp(`require\\(['"]${depNameEscaped}`, 'g');
|
|
318
|
+
|
|
319
|
+
if (importPattern.test(content) || requirePattern.test(content)) {
|
|
320
|
+
foundInTests = true;
|
|
321
|
+
break;
|
|
322
|
+
}
|
|
323
|
+
} catch (error) {
|
|
324
|
+
// Skip files we can't read
|
|
325
|
+
}
|
|
326
|
+
}
|
|
327
|
+
|
|
328
|
+
return foundInTests;
|
|
329
|
+
}
|
|
330
|
+
|
|
183
331
|
const dependenciesCheck = {
|
|
184
332
|
name: 'dependencies',
|
|
185
|
-
description: 'Dependency analysis (outdated versions, security, unused deps)',
|
|
333
|
+
description: 'Dependency analysis (outdated versions, security, unused deps, redundant deps, misclassified deps, version mismatches)',
|
|
186
334
|
severity: 'warning',
|
|
187
335
|
|
|
188
336
|
async run(context) {
|
|
@@ -207,9 +355,115 @@ const dependenciesCheck = {
|
|
|
207
355
|
// Get pace-core peer dependencies
|
|
208
356
|
const paceCorePeerDeps = getPaceCorePeerDeps(projectRoot);
|
|
209
357
|
|
|
358
|
+
// Get pace-core version (used in multiple checks)
|
|
359
|
+
const paceCoreVersion = !isPaceCoreRepository ? allDeps['@jmruthers/pace-core'] : null;
|
|
360
|
+
|
|
361
|
+
// Check for required React version (19.2.3) - do this early to ensure it always runs
|
|
362
|
+
// This check MUST run for all consuming apps (not pace-core repository itself)
|
|
363
|
+
if (!isPaceCoreRepository) {
|
|
364
|
+
const requiredReactVersion = '19.2.3';
|
|
365
|
+
|
|
366
|
+
// Read React versions from package.json - check both dependencies and devDependencies
|
|
367
|
+
const reactVersion = (packageJson.dependencies && packageJson.dependencies.react) ||
|
|
368
|
+
(packageJson.devDependencies && packageJson.devDependencies.react) ||
|
|
369
|
+
null;
|
|
370
|
+
const reactDomVersion = (packageJson.dependencies && packageJson.dependencies['react-dom']) ||
|
|
371
|
+
(packageJson.devDependencies && packageJson.devDependencies['react-dom']) ||
|
|
372
|
+
null;
|
|
373
|
+
|
|
374
|
+
// Normalize version for comparison (remove ^, ~, >=, <=, etc.)
|
|
375
|
+
const normalizeVersion = (v) => {
|
|
376
|
+
if (!v || typeof v !== 'string') {
|
|
377
|
+
return null;
|
|
378
|
+
}
|
|
379
|
+
// Remove range prefixes (^, ~, >=, <=, >, <, =)
|
|
380
|
+
let normalized = v.replace(/^[\^~>=<]/, '');
|
|
381
|
+
// Remove any whitespace
|
|
382
|
+
normalized = normalized.trim();
|
|
383
|
+
// Extract base version (e.g., "19.2.3" from "19.2.3" or "19.2.3-alpha.1")
|
|
384
|
+
const match = normalized.match(/^(\d+\.\d+\.\d+)/);
|
|
385
|
+
if (match && match[1]) {
|
|
386
|
+
return match[1];
|
|
387
|
+
}
|
|
388
|
+
// Fallback: try to extract any version pattern
|
|
389
|
+
const fallbackMatch = normalized.match(/(\d+\.\d+\.\d+)/);
|
|
390
|
+
return fallbackMatch ? fallbackMatch[1] : null;
|
|
391
|
+
};
|
|
392
|
+
|
|
393
|
+
// Check React version
|
|
394
|
+
if (reactVersion) {
|
|
395
|
+
const normalizedReactVersion = normalizeVersion(reactVersion);
|
|
396
|
+
const normalizedRequiredVersion = normalizeVersion(requiredReactVersion);
|
|
397
|
+
|
|
398
|
+
// Check if versions match - always add issue if they don't match
|
|
399
|
+
let versionMismatch = false;
|
|
400
|
+
|
|
401
|
+
if (normalizedReactVersion && normalizedRequiredVersion) {
|
|
402
|
+
// Both normalized successfully - compare normalized versions
|
|
403
|
+
versionMismatch = normalizedReactVersion !== normalizedRequiredVersion;
|
|
404
|
+
} else {
|
|
405
|
+
// Normalization failed - do a simple string check
|
|
406
|
+
const cleanReactVersion = reactVersion.replace(/^[\^~>=<]/, '').trim();
|
|
407
|
+
const cleanRequiredVersion = requiredReactVersion.replace(/^[\^~>=<]/, '').trim();
|
|
408
|
+
versionMismatch = cleanReactVersion !== cleanRequiredVersion && !cleanReactVersion.startsWith(requiredReactVersion);
|
|
409
|
+
}
|
|
410
|
+
|
|
411
|
+
if (versionMismatch) {
|
|
412
|
+
issues.push({
|
|
413
|
+
type: 'incorrect-react-version',
|
|
414
|
+
file: 'package.json',
|
|
415
|
+
message: `React version ${reactVersion} does not match required version ${requiredReactVersion}`,
|
|
416
|
+
recommendation: `Install React ${requiredReactVersion}. Run: npm install react@${requiredReactVersion} react-dom@${requiredReactVersion}`
|
|
417
|
+
});
|
|
418
|
+
}
|
|
419
|
+
} else {
|
|
420
|
+
issues.push({
|
|
421
|
+
type: 'missing-react',
|
|
422
|
+
file: 'package.json',
|
|
423
|
+
message: 'React is not installed but is required',
|
|
424
|
+
recommendation: `Install React ${requiredReactVersion}. Run: npm install react@${requiredReactVersion} react-dom@${requiredReactVersion}`
|
|
425
|
+
});
|
|
426
|
+
}
|
|
427
|
+
|
|
428
|
+
// Check react-dom version matches
|
|
429
|
+
if (reactDomVersion) {
|
|
430
|
+
const normalizedReactDomVersion = normalizeVersion(reactDomVersion);
|
|
431
|
+
const normalizedRequiredVersion = normalizeVersion(requiredReactVersion);
|
|
432
|
+
|
|
433
|
+
// Check if versions match - always add issue if they don't match
|
|
434
|
+
let versionMismatch = false;
|
|
435
|
+
|
|
436
|
+
if (normalizedReactDomVersion && normalizedRequiredVersion) {
|
|
437
|
+
// Both normalized successfully - compare normalized versions
|
|
438
|
+
versionMismatch = normalizedReactDomVersion !== normalizedRequiredVersion;
|
|
439
|
+
} else {
|
|
440
|
+
// Normalization failed - do a simple string check
|
|
441
|
+
const cleanReactDomVersion = reactDomVersion.replace(/^[\^~>=<]/, '').trim();
|
|
442
|
+
const cleanRequiredVersion = requiredReactVersion.replace(/^[\^~>=<]/, '').trim();
|
|
443
|
+
versionMismatch = cleanReactDomVersion !== cleanRequiredVersion && !cleanReactDomVersion.startsWith(requiredReactVersion);
|
|
444
|
+
}
|
|
445
|
+
|
|
446
|
+
if (versionMismatch) {
|
|
447
|
+
issues.push({
|
|
448
|
+
type: 'incorrect-react-dom-version',
|
|
449
|
+
file: 'package.json',
|
|
450
|
+
message: `react-dom version ${reactDomVersion} does not match required version ${requiredReactVersion}`,
|
|
451
|
+
recommendation: `Install react-dom ${requiredReactVersion}. Run: npm install react-dom@${requiredReactVersion}`
|
|
452
|
+
});
|
|
453
|
+
}
|
|
454
|
+
} else if (reactVersion) {
|
|
455
|
+
// If react is installed but react-dom is not, that's also an issue
|
|
456
|
+
issues.push({
|
|
457
|
+
type: 'missing-react-dom',
|
|
458
|
+
file: 'package.json',
|
|
459
|
+
message: 'react-dom is not installed but is required',
|
|
460
|
+
recommendation: `Install react-dom ${requiredReactVersion}. Run: npm install react-dom@${requiredReactVersion}`
|
|
461
|
+
});
|
|
462
|
+
}
|
|
463
|
+
}
|
|
464
|
+
|
|
210
465
|
// Check pace-core version (skip if this is the pace-core repository itself)
|
|
211
466
|
if (!isPaceCoreRepository) {
|
|
212
|
-
const paceCoreVersion = allDeps['@jmruthers/pace-core'];
|
|
213
467
|
if (paceCoreVersion) {
|
|
214
468
|
// Check if version is outdated (simplified - would need to check npm registry)
|
|
215
469
|
if (paceCoreVersion.startsWith('^') || paceCoreVersion.startsWith('~')) {
|
|
@@ -251,10 +505,15 @@ const dependenciesCheck = {
|
|
|
251
505
|
// Also scan config files
|
|
252
506
|
const configFiles = findConfigFiles(projectRoot);
|
|
253
507
|
|
|
254
|
-
//
|
|
508
|
+
// Find test files separately for better analysis
|
|
509
|
+
const testFiles = !isPaceCoreRepository ? findTestFiles(projectRoot) : [];
|
|
510
|
+
|
|
511
|
+
// Build set of imported packages from source files (runtime)
|
|
255
512
|
const importedPackages = new Set();
|
|
513
|
+
// Build set of packages used only in tests
|
|
514
|
+
const testOnlyPackages = new Set();
|
|
256
515
|
|
|
257
|
-
// Scan source files
|
|
516
|
+
// Scan source files (runtime code)
|
|
258
517
|
sourceFiles.forEach(filePath => {
|
|
259
518
|
try {
|
|
260
519
|
const content = fs.readFileSync(filePath, 'utf8');
|
|
@@ -277,6 +536,42 @@ const dependenciesCheck = {
|
|
|
277
536
|
importedPackages.add(pkgName);
|
|
278
537
|
}
|
|
279
538
|
}
|
|
539
|
+
// Check for dynamic imports
|
|
540
|
+
const dynamicImportPattern = /import\(['"]([^'"]+)['"]\)/g;
|
|
541
|
+
while ((match = dynamicImportPattern.exec(content)) !== null) {
|
|
542
|
+
const modulePath = match[1];
|
|
543
|
+
const pkgName = extractPackageName(modulePath);
|
|
544
|
+
if (pkgName) {
|
|
545
|
+
importedPackages.add(pkgName);
|
|
546
|
+
}
|
|
547
|
+
}
|
|
548
|
+
} catch (error) {
|
|
549
|
+
// Skip files with errors
|
|
550
|
+
}
|
|
551
|
+
});
|
|
552
|
+
|
|
553
|
+
// Scan test files separately
|
|
554
|
+
testFiles.forEach(filePath => {
|
|
555
|
+
try {
|
|
556
|
+
const content = fs.readFileSync(filePath, 'utf8');
|
|
557
|
+
const importPattern = /from\s+['"]([^'"]+)['"]/g;
|
|
558
|
+
let match;
|
|
559
|
+
while ((match = importPattern.exec(content)) !== null) {
|
|
560
|
+
const modulePath = match[1];
|
|
561
|
+
const pkgName = extractPackageName(modulePath);
|
|
562
|
+
if (pkgName && !importedPackages.has(pkgName)) {
|
|
563
|
+
// Only add to test-only if not already used in runtime code
|
|
564
|
+
testOnlyPackages.add(pkgName);
|
|
565
|
+
}
|
|
566
|
+
}
|
|
567
|
+
const requirePattern = /require\(['"]([^'"]+)['"]\)/g;
|
|
568
|
+
while ((match = requirePattern.exec(content)) !== null) {
|
|
569
|
+
const modulePath = match[1];
|
|
570
|
+
const pkgName = extractPackageName(modulePath);
|
|
571
|
+
if (pkgName && !importedPackages.has(pkgName)) {
|
|
572
|
+
testOnlyPackages.add(pkgName);
|
|
573
|
+
}
|
|
574
|
+
}
|
|
280
575
|
} catch (error) {
|
|
281
576
|
// Skip files with errors
|
|
282
577
|
}
|
|
@@ -353,8 +648,14 @@ const dependenciesCheck = {
|
|
|
353
648
|
return;
|
|
354
649
|
}
|
|
355
650
|
|
|
356
|
-
// Check if package is imported in source files
|
|
651
|
+
// Check if package is imported in source files (runtime)
|
|
357
652
|
if (importedPackages.has(dep)) {
|
|
653
|
+
// Package is used in runtime code, so it's needed
|
|
654
|
+
return;
|
|
655
|
+
}
|
|
656
|
+
|
|
657
|
+
// Check if it's used in package.json scripts
|
|
658
|
+
if (checkPackageScripts(packageJson, dep)) {
|
|
358
659
|
return;
|
|
359
660
|
}
|
|
360
661
|
|
|
@@ -368,6 +669,10 @@ const dependenciesCheck = {
|
|
|
368
669
|
return;
|
|
369
670
|
}
|
|
370
671
|
|
|
672
|
+
// Check if it's used in test files
|
|
673
|
+
const isUsedInTests = testOnlyPackages.has(dep) || isOnlyUsedInTests(projectRoot, dep, testFiles);
|
|
674
|
+
const isOnlyInTests = isUsedInTests && !importedPackages.has(dep);
|
|
675
|
+
|
|
371
676
|
// For dev dependencies, provide more context
|
|
372
677
|
const isDevDep = dep in devDeps;
|
|
373
678
|
const context = [];
|
|
@@ -381,26 +686,37 @@ const dependenciesCheck = {
|
|
|
381
686
|
context.push('This may be required by @jmruthers/pace-core as a transitive dependency');
|
|
382
687
|
}
|
|
383
688
|
|
|
689
|
+
// Check if it's in vite.config.ts optimizeDeps (common for pace-core peer deps)
|
|
690
|
+
const viteConfigPath = path.join(projectRoot, 'vite.config.ts');
|
|
691
|
+
const viteConfigJsPath = path.join(projectRoot, 'vite.config.js');
|
|
692
|
+
let isInViteConfig = false;
|
|
693
|
+
|
|
694
|
+
for (const configPath of [viteConfigPath, viteConfigJsPath]) {
|
|
695
|
+
if (fs.existsSync(configPath)) {
|
|
696
|
+
try {
|
|
697
|
+
const viteContent = fs.readFileSync(configPath, 'utf8');
|
|
698
|
+
if (viteContent.includes(dep)) {
|
|
699
|
+
isInViteConfig = true;
|
|
700
|
+
break;
|
|
701
|
+
}
|
|
702
|
+
} catch (error) {
|
|
703
|
+
// Skip if can't read
|
|
704
|
+
}
|
|
705
|
+
}
|
|
706
|
+
}
|
|
707
|
+
|
|
384
708
|
// Only warn if we're reasonably sure it's unused
|
|
385
709
|
// For runtime dependencies, be more strict
|
|
386
710
|
if (!isDevDep) {
|
|
387
|
-
//
|
|
388
|
-
|
|
389
|
-
|
|
390
|
-
|
|
391
|
-
|
|
392
|
-
|
|
393
|
-
|
|
394
|
-
|
|
395
|
-
|
|
396
|
-
if (viteContent.includes(dep)) {
|
|
397
|
-
isInViteConfig = true;
|
|
398
|
-
break;
|
|
399
|
-
}
|
|
400
|
-
} catch (error) {
|
|
401
|
-
// Skip if can't read
|
|
402
|
-
}
|
|
403
|
-
}
|
|
711
|
+
// If it's only used in tests, suggest moving to devDependencies
|
|
712
|
+
if (isOnlyInTests) {
|
|
713
|
+
warnings.push({
|
|
714
|
+
type: 'test-only-runtime-dependency',
|
|
715
|
+
file: 'package.json',
|
|
716
|
+
message: `Runtime dependency '${dep}' is only used in test files`,
|
|
717
|
+
recommendation: `Move '${dep}' from dependencies to devDependencies since it's only used in tests. This will reduce your production bundle size.`
|
|
718
|
+
});
|
|
719
|
+
return;
|
|
404
720
|
}
|
|
405
721
|
|
|
406
722
|
const recommendation = `Verify if '${dep}' is actually used. ${context.length > 0 ? context.join(' ') : ''}${isInViteConfig ? 'This dependency is referenced in vite.config.ts, which suggests it may be required by pace-core components. ' : ''}If it's a peer dependency of @jmruthers/pace-core (check pace-core's package.json), it's required even if not directly imported. If confirmed unused, remove it to reduce bundle size.`;
|
|
@@ -417,7 +733,7 @@ const dependenciesCheck = {
|
|
|
417
733
|
type: 'potentially-unused-dev-dependency',
|
|
418
734
|
file: 'package.json',
|
|
419
735
|
message: `Dev dependency '${dep}' may be unused`,
|
|
420
|
-
recommendation: `Check if '${dep}' is used in build configs, test files, or by pace-core. ${context.join(' ')}If confirmed unused, you can remove it.`
|
|
736
|
+
recommendation: `Check if '${dep}' is used in build configs, test files, or by pace-core. ${context.join(' ')}${isInViteConfig ? 'This dependency is referenced in vite.config.ts. ' : ''}If confirmed unused, you can remove it.`
|
|
421
737
|
});
|
|
422
738
|
}
|
|
423
739
|
});
|
|
@@ -435,18 +751,233 @@ const dependenciesCheck = {
|
|
|
435
751
|
}
|
|
436
752
|
});
|
|
437
753
|
|
|
438
|
-
// Check for version mismatches
|
|
439
|
-
|
|
440
|
-
const
|
|
441
|
-
|
|
754
|
+
// Check for version mismatches between react and react-dom
|
|
755
|
+
// (The required version check is done earlier in the function)
|
|
756
|
+
const reactVersionForMismatch = packageJson.dependencies?.react || packageJson.devDependencies?.react;
|
|
757
|
+
const reactDomVersionForMismatch = packageJson.dependencies?.['react-dom'] || packageJson.devDependencies?.['react-dom'];
|
|
758
|
+
|
|
759
|
+
if (reactVersionForMismatch && reactDomVersionForMismatch && reactVersionForMismatch !== reactDomVersionForMismatch) {
|
|
442
760
|
issues.push({
|
|
443
761
|
type: 'version-mismatch',
|
|
444
762
|
file: 'package.json',
|
|
445
|
-
message: `React version mismatch: react@${
|
|
763
|
+
message: `React version mismatch: react@${reactVersionForMismatch} vs react-dom@${reactDomVersionForMismatch}`,
|
|
446
764
|
recommendation: 'React and react-dom versions must match exactly'
|
|
447
765
|
});
|
|
448
766
|
}
|
|
449
767
|
|
|
768
|
+
// ============================================
|
|
769
|
+
// NEW CHECKS: Future-proofing dependency management
|
|
770
|
+
// ============================================
|
|
771
|
+
|
|
772
|
+
// 1. Check for redundant dependencies in consuming apps
|
|
773
|
+
// (packages already provided by pace-core)
|
|
774
|
+
if (!isPaceCoreRepository) {
|
|
775
|
+
const paceCoreDeps = getPaceCoreDeps(projectRoot);
|
|
776
|
+
const paceCorePeerDeps = getPaceCorePeerDeps(projectRoot);
|
|
777
|
+
|
|
778
|
+
// Packages that pace-core provides as dependencies (should not be duplicated)
|
|
779
|
+
const redundantDeps = [];
|
|
780
|
+
Object.keys(packageJson.dependencies || {}).forEach(dep => {
|
|
781
|
+
// Skip pace-core itself
|
|
782
|
+
if (dep === '@jmruthers/pace-core') {
|
|
783
|
+
return;
|
|
784
|
+
}
|
|
785
|
+
|
|
786
|
+
// Check if it's provided by pace-core as a dependency
|
|
787
|
+
if (paceCoreDeps[dep]) {
|
|
788
|
+
redundantDeps.push({
|
|
789
|
+
dep,
|
|
790
|
+
reason: 'provided by pace-core as a dependency',
|
|
791
|
+
paceCoreVersion: paceCoreDeps[dep]
|
|
792
|
+
});
|
|
793
|
+
}
|
|
794
|
+
});
|
|
795
|
+
|
|
796
|
+
redundantDeps.forEach(({ dep, reason, paceCoreVersion }) => {
|
|
797
|
+
warnings.push({
|
|
798
|
+
type: 'redundant-dependency',
|
|
799
|
+
file: 'package.json',
|
|
800
|
+
message: `Dependency '${dep}' is redundant - ${reason}`,
|
|
801
|
+
recommendation: `Remove '${dep}' from dependencies. It's already provided by @jmruthers/pace-core@${paceCoreVersion}. Exception: If you need a different version or direct control, keep it but document why.`
|
|
802
|
+
});
|
|
803
|
+
});
|
|
804
|
+
}
|
|
805
|
+
|
|
806
|
+
// 2. Check for missing dependencies (used but not declared)
|
|
807
|
+
if (!isPaceCoreRepository) {
|
|
808
|
+
// Check for dynamic imports that might need to be declared
|
|
809
|
+
const optionalDeps = ['recharts', 'papaparse', 'lodash.debounce', 'lodash.throttle'];
|
|
810
|
+
const dynamicImportPattern = /import\(['"]([^'"]+)['"]\)/g;
|
|
811
|
+
|
|
812
|
+
sourceFiles.forEach(filePath => {
|
|
813
|
+
try {
|
|
814
|
+
const content = fs.readFileSync(filePath, 'utf8');
|
|
815
|
+
let match;
|
|
816
|
+
while ((match = dynamicImportPattern.exec(content)) !== null) {
|
|
817
|
+
const modulePath = match[1];
|
|
818
|
+
const pkgName = extractPackageName(modulePath);
|
|
819
|
+
if (pkgName && optionalDeps.includes(pkgName)) {
|
|
820
|
+
// Check if it's declared
|
|
821
|
+
if (!allDeps[pkgName]) {
|
|
822
|
+
suggestions.push({
|
|
823
|
+
type: 'missing-optional-dependency',
|
|
824
|
+
file: path.relative(projectRoot, filePath),
|
|
825
|
+
message: `Optional dependency '${pkgName}' is dynamically imported but not declared`,
|
|
826
|
+
recommendation: `Add '${pkgName}' to dependencies if you use this feature, or ensure it's marked as external in your build config`
|
|
827
|
+
});
|
|
828
|
+
}
|
|
829
|
+
}
|
|
830
|
+
}
|
|
831
|
+
} catch (error) {
|
|
832
|
+
// Skip files with errors
|
|
833
|
+
}
|
|
834
|
+
});
|
|
835
|
+
}
|
|
836
|
+
|
|
837
|
+
// 3. Check for misclassified dependencies
|
|
838
|
+
// Runtime deps in devDependencies, peer deps in dependencies, etc.
|
|
839
|
+
if (isPaceCoreRepository) {
|
|
840
|
+
// Check pace-core's own package.json
|
|
841
|
+
const packagesCorePath = path.join(projectRoot, 'packages', 'core', 'package.json');
|
|
842
|
+
if (fs.existsSync(packagesCorePath)) {
|
|
843
|
+
const corePkg = JSON.parse(fs.readFileSync(packagesCorePath, 'utf8'));
|
|
844
|
+
const coreDeps = corePkg.dependencies || {};
|
|
845
|
+
const coreDevDeps = corePkg.devDependencies || {};
|
|
846
|
+
const corePeerDeps = corePkg.peerDependencies || {};
|
|
847
|
+
|
|
848
|
+
// Check if peer dependencies are incorrectly in dependencies
|
|
849
|
+
Object.keys(coreDeps).forEach(dep => {
|
|
850
|
+
if (corePeerDeps[dep]) {
|
|
851
|
+
issues.push({
|
|
852
|
+
type: 'misclassified-dependency',
|
|
853
|
+
file: 'packages/core/package.json',
|
|
854
|
+
message: `'${dep}' is declared as both dependency and peerDependency`,
|
|
855
|
+
recommendation: `Remove '${dep}' from dependencies. Peer dependencies should only be in peerDependencies.`
|
|
856
|
+
});
|
|
857
|
+
}
|
|
858
|
+
});
|
|
859
|
+
|
|
860
|
+
// Check if runtime dependencies are in devDependencies
|
|
861
|
+
// This is harder to detect automatically, but we can check for common patterns
|
|
862
|
+
const runtimeDepsInDev = ['@hookform/resolvers', '@supabase/supabase-js', '@tanstack/react-query'];
|
|
863
|
+
runtimeDepsInDev.forEach(dep => {
|
|
864
|
+
if (coreDevDeps[dep] && !coreDeps[dep]) {
|
|
865
|
+
// Check if it's used in source code
|
|
866
|
+
const coreSrcPath = path.join(projectRoot, 'packages', 'core', 'src');
|
|
867
|
+
if (fs.existsSync(coreSrcPath)) {
|
|
868
|
+
const coreSourceFiles = findSourceFiles(coreSrcPath);
|
|
869
|
+
let isUsed = false;
|
|
870
|
+
coreSourceFiles.forEach(filePath => {
|
|
871
|
+
try {
|
|
872
|
+
const content = fs.readFileSync(filePath, 'utf8');
|
|
873
|
+
if (content.includes(dep) || content.includes(extractPackageName(dep))) {
|
|
874
|
+
isUsed = true;
|
|
875
|
+
}
|
|
876
|
+
} catch (error) {
|
|
877
|
+
// Skip
|
|
878
|
+
}
|
|
879
|
+
});
|
|
880
|
+
|
|
881
|
+
if (isUsed) {
|
|
882
|
+
warnings.push({
|
|
883
|
+
type: 'misclassified-dependency',
|
|
884
|
+
file: 'packages/core/package.json',
|
|
885
|
+
message: `'${dep}' is in devDependencies but appears to be used in source code`,
|
|
886
|
+
recommendation: `Move '${dep}' from devDependencies to dependencies if it's used at runtime`
|
|
887
|
+
});
|
|
888
|
+
}
|
|
889
|
+
}
|
|
890
|
+
}
|
|
891
|
+
});
|
|
892
|
+
}
|
|
893
|
+
}
|
|
894
|
+
|
|
895
|
+
// 4. Check for version mismatches across workspace (monorepo)
|
|
896
|
+
if (isPaceCoreRepository) {
|
|
897
|
+
const rootPkg = getWorkspaceRootPackageJson(projectRoot);
|
|
898
|
+
const corePkg = getPaceCorePackageJson(projectRoot);
|
|
899
|
+
|
|
900
|
+
if (rootPkg && corePkg) {
|
|
901
|
+
const rootDeps = { ...rootPkg.dependencies, ...rootPkg.devDependencies };
|
|
902
|
+
const coreDeps = { ...corePkg.dependencies, ...corePkg.devDependencies };
|
|
903
|
+
|
|
904
|
+
// Check for version mismatches in common dependencies
|
|
905
|
+
const commonDeps = Object.keys(rootDeps).filter(dep => coreDeps[dep]);
|
|
906
|
+
commonDeps.forEach(dep => {
|
|
907
|
+
const rootVersion = rootDeps[dep];
|
|
908
|
+
const coreVersion = coreDeps[dep];
|
|
909
|
+
|
|
910
|
+
// Normalize versions for comparison (remove ^, ~, etc.)
|
|
911
|
+
const normalizeVersion = (v) => v.replace(/^[\^~]/, '');
|
|
912
|
+
const rootNormalized = normalizeVersion(rootVersion);
|
|
913
|
+
const coreNormalized = normalizeVersion(coreVersion);
|
|
914
|
+
|
|
915
|
+
if (rootNormalized !== coreNormalized) {
|
|
916
|
+
warnings.push({
|
|
917
|
+
type: 'workspace-version-mismatch',
|
|
918
|
+
file: 'package.json',
|
|
919
|
+
message: `Version mismatch for '${dep}': root@${rootVersion} vs packages/core@${coreVersion}`,
|
|
920
|
+
recommendation: `Align versions across workspace. Consider using the same version in both root and packages/core package.json files.`
|
|
921
|
+
});
|
|
922
|
+
}
|
|
923
|
+
});
|
|
924
|
+
}
|
|
925
|
+
}
|
|
926
|
+
|
|
927
|
+
// 5. Check for missing peer dependencies in consuming apps
|
|
928
|
+
if (!isPaceCoreRepository) {
|
|
929
|
+
const paceCorePkg = getPaceCorePackageJson(projectRoot);
|
|
930
|
+
if (paceCorePkg) {
|
|
931
|
+
const peerDeps = paceCorePkg.peerDependencies || {};
|
|
932
|
+
const allDeps = { ...packageJson.dependencies, ...packageJson.devDependencies };
|
|
933
|
+
|
|
934
|
+
Object.keys(peerDeps).forEach(peerDep => {
|
|
935
|
+
if (!allDeps[peerDep]) {
|
|
936
|
+
issues.push({
|
|
937
|
+
type: 'missing-peer-dependency',
|
|
938
|
+
file: 'package.json',
|
|
939
|
+
message: `Missing peer dependency '${peerDep}' required by @jmruthers/pace-core`,
|
|
940
|
+
recommendation: `Install '${peerDep}@${peerDeps[peerDep]}' as a dependency. Run: npm install ${peerDep}@${peerDeps[peerDep]}`
|
|
941
|
+
});
|
|
942
|
+
}
|
|
943
|
+
});
|
|
944
|
+
}
|
|
945
|
+
}
|
|
946
|
+
|
|
947
|
+
// 6. Check for optional dependencies handling in vite.config
|
|
948
|
+
if (!isPaceCoreRepository) {
|
|
949
|
+
const viteConfigPath = path.join(projectRoot, 'vite.config.ts');
|
|
950
|
+
const viteConfigJsPath = path.join(projectRoot, 'vite.config.js');
|
|
951
|
+
const optionalDeps = ['recharts', 'papaparse'];
|
|
952
|
+
|
|
953
|
+
for (const configPath of [viteConfigPath, viteConfigJsPath]) {
|
|
954
|
+
if (fs.existsSync(configPath)) {
|
|
955
|
+
try {
|
|
956
|
+
const viteContent = fs.readFileSync(configPath, 'utf8');
|
|
957
|
+
optionalDeps.forEach(dep => {
|
|
958
|
+
// Check if it's marked as external
|
|
959
|
+
const isExternal = viteContent.includes(`external`) &&
|
|
960
|
+
(viteContent.includes(`'${dep}'`) || viteContent.includes(`"${dep}"`));
|
|
961
|
+
|
|
962
|
+
// Check if it's in dependencies
|
|
963
|
+
const isInDeps = allDeps[dep];
|
|
964
|
+
|
|
965
|
+
if (!isExternal && !isInDeps) {
|
|
966
|
+
suggestions.push({
|
|
967
|
+
type: 'optional-dependency-config',
|
|
968
|
+
file: path.relative(projectRoot, configPath),
|
|
969
|
+
message: `Optional dependency '${dep}' should be handled in build config`,
|
|
970
|
+
recommendation: `Add '${dep}' to build.rollupOptions.external if you want it resolved at runtime, or install it as a dependency if you want it bundled`
|
|
971
|
+
});
|
|
972
|
+
}
|
|
973
|
+
});
|
|
974
|
+
} catch (error) {
|
|
975
|
+
// Skip if can't read
|
|
976
|
+
}
|
|
977
|
+
}
|
|
978
|
+
}
|
|
979
|
+
}
|
|
980
|
+
|
|
450
981
|
return { issues, warnings, suggestions };
|
|
451
982
|
}
|
|
452
983
|
};
|