@jmruthers/pace-core 0.5.183 → 0.5.185

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.
Files changed (307) hide show
  1. package/CHANGELOG.md +38 -0
  2. package/README.md +60 -1
  3. package/core-usage-manifest.json +312 -0
  4. package/dist/{DataTable-QAB34V6K.js → DataTable-IX2NBUTP.js} +6 -6
  5. package/dist/{DataTable-Bz8ffqyA.d.ts → DataTable-Z9NLVJh0.d.ts} +1 -1
  6. package/dist/{index-Bl--n7-T.d.ts → PublicPageProvider-BABf6JCh.d.ts} +21 -10
  7. package/dist/{UnifiedAuthProvider-7F6T4B6K.js → UnifiedAuthProvider-A4BCQRJY.js} +4 -2
  8. package/dist/{UnifiedAuthProvider-F86d7dSi.d.ts → UnifiedAuthProvider-BG0AL5eE.d.ts} +2 -1
  9. package/dist/{api-ROMBCNKU.js → api-BMFCXVQX.js} +2 -2
  10. package/dist/{chunk-RA3JUFMW.js → chunk-445GEP27.js} +154 -4
  11. package/dist/{chunk-RA3JUFMW.js.map → chunk-445GEP27.js.map} +1 -1
  12. package/dist/{chunk-CSOFYHAG.js → chunk-AISXLWGZ.js} +374 -60
  13. package/dist/chunk-AISXLWGZ.js.map +1 -0
  14. package/dist/{chunk-FUEYYMX5.js → chunk-FXFJRTKI.js} +24 -3
  15. package/dist/chunk-FXFJRTKI.js.map +1 -0
  16. package/dist/{chunk-QETLRQI6.js → chunk-HC67NW5K.js} +380 -360
  17. package/dist/chunk-HC67NW5K.js.map +1 -0
  18. package/dist/chunk-HESYZWZW.js +388 -0
  19. package/dist/chunk-HESYZWZW.js.map +1 -0
  20. package/dist/{chunk-QUVSNGIP.js → chunk-HGPQUCBC.js} +34 -9
  21. package/dist/{chunk-QUVSNGIP.js.map → chunk-HGPQUCBC.js.map} +1 -1
  22. package/dist/{chunk-UHNYIBXL.js → chunk-IXSNYUCT.js} +1 -1
  23. package/dist/chunk-IXSNYUCT.js.map +1 -0
  24. package/dist/{chunk-MI7HBHN3.js → chunk-MX3EIJGQ.js} +4 -3
  25. package/dist/{chunk-MI7HBHN3.js.map → chunk-MX3EIJGQ.js.map} +1 -1
  26. package/dist/{chunk-PWAHJW4G.js → chunk-OKI34GZD.js} +86 -33
  27. package/dist/chunk-OKI34GZD.js.map +1 -0
  28. package/dist/{chunk-W22JP75J.js → chunk-STTZQK2I.js} +3 -3
  29. package/dist/chunk-THRPYOFK.js +215 -0
  30. package/dist/chunk-THRPYOFK.js.map +1 -0
  31. package/dist/{chunk-M7W4CP3M.js → chunk-U6WNSFX5.js} +2 -1
  32. package/dist/chunk-U6WNSFX5.js.map +1 -0
  33. package/dist/{chunk-QCDXODCA.js → chunk-XAUHJD3L.js} +2 -2
  34. package/dist/components.d.ts +182 -6
  35. package/dist/components.js +157 -11
  36. package/dist/components.js.map +1 -1
  37. package/dist/eslint-rules/pace-core-compliance.cjs +406 -0
  38. package/dist/{file-reference-D06mEEWW.d.ts → file-reference-BjR39ktt.d.ts} +7 -1
  39. package/dist/hooks.d.ts +7 -14
  40. package/dist/hooks.js +10 -22
  41. package/dist/hooks.js.map +1 -1
  42. package/dist/index.d.ts +11 -11
  43. package/dist/index.js +79 -16
  44. package/dist/index.js.map +1 -1
  45. package/dist/providers.d.ts +1 -1
  46. package/dist/providers.js +3 -1
  47. package/dist/rbac/index.d.ts +205 -14
  48. package/dist/rbac/index.js +28 -6
  49. package/dist/timezone-_pgH8qrY.d.ts +530 -0
  50. package/dist/{types-_x1f4QBF.d.ts → types-DUyCRSTj.d.ts} +1 -1
  51. package/dist/types.d.ts +1 -1
  52. package/dist/types.js +1 -1
  53. package/dist/{usePublicRouteParams-JJczomYq.d.ts → usePublicRouteParams-CvnC3d-e.d.ts} +113 -2
  54. package/dist/utils.d.ts +109 -151
  55. package/dist/utils.js +128 -138
  56. package/dist/utils.js.map +1 -1
  57. package/docs/api/README.md +60 -1
  58. package/docs/api/classes/ColumnFactory.md +1 -1
  59. package/docs/api/classes/ErrorBoundary.md +1 -1
  60. package/docs/api/classes/InvalidScopeError.md +1 -1
  61. package/docs/api/classes/Logger.md +178 -0
  62. package/docs/api/classes/MissingUserContextError.md +1 -1
  63. package/docs/api/classes/OrganisationContextRequiredError.md +1 -1
  64. package/docs/api/classes/PermissionDeniedError.md +1 -1
  65. package/docs/api/classes/RBACAuditManager.md +2 -2
  66. package/docs/api/classes/RBACCache.md +1 -1
  67. package/docs/api/classes/RBACEngine.md +2 -2
  68. package/docs/api/classes/RBACError.md +1 -1
  69. package/docs/api/classes/RBACNotInitializedError.md +1 -1
  70. package/docs/api/classes/SecureSupabaseClient.md +5 -5
  71. package/docs/api/classes/StorageUtils.md +1 -1
  72. package/docs/api/enums/FileCategory.md +1 -1
  73. package/docs/api/enums/LogLevel.md +54 -0
  74. package/docs/api/enums/RBACErrorCode.md +1 -1
  75. package/docs/api/enums/RPCFunction.md +1 -1
  76. package/docs/api/interfaces/AggregateConfig.md +1 -1
  77. package/docs/api/interfaces/BadgeProps.md +1 -1
  78. package/docs/api/interfaces/ButtonProps.md +1 -1
  79. package/docs/api/interfaces/CalendarProps.md +18 -2
  80. package/docs/api/interfaces/CardProps.md +1 -1
  81. package/docs/api/interfaces/ColorPalette.md +1 -1
  82. package/docs/api/interfaces/ColorShade.md +1 -1
  83. package/docs/api/interfaces/ComplianceResult.md +30 -0
  84. package/docs/api/interfaces/DataAccessRecord.md +1 -1
  85. package/docs/api/interfaces/DataRecord.md +1 -1
  86. package/docs/api/interfaces/DataTableAction.md +1 -1
  87. package/docs/api/interfaces/DataTableColumn.md +1 -1
  88. package/docs/api/interfaces/DataTableProps.md +1 -1
  89. package/docs/api/interfaces/DataTableToolbarButton.md +1 -1
  90. package/docs/api/interfaces/DatabaseComplianceResult.md +85 -0
  91. package/docs/api/interfaces/DatabaseIssue.md +41 -0
  92. package/docs/api/interfaces/EmptyStateConfig.md +1 -1
  93. package/docs/api/interfaces/EnhancedNavigationMenuProps.md +1 -1
  94. package/docs/api/interfaces/EventAppRoleData.md +6 -6
  95. package/docs/api/interfaces/ExportColumn.md +1 -1
  96. package/docs/api/interfaces/ExportOptions.md +1 -1
  97. package/docs/api/interfaces/FileDisplayProps.md +1 -1
  98. package/docs/api/interfaces/FileMetadata.md +1 -1
  99. package/docs/api/interfaces/FileReference.md +1 -1
  100. package/docs/api/interfaces/FileSizeLimits.md +1 -1
  101. package/docs/api/interfaces/FileUploadOptions.md +24 -8
  102. package/docs/api/interfaces/FileUploadProps.md +24 -13
  103. package/docs/api/interfaces/FooterProps.md +1 -1
  104. package/docs/api/interfaces/FormFieldProps.md +1 -1
  105. package/docs/api/interfaces/FormProps.md +1 -1
  106. package/docs/api/interfaces/GrantEventAppRoleParams.md +9 -9
  107. package/docs/api/interfaces/InactivityWarningModalProps.md +1 -1
  108. package/docs/api/interfaces/InputProps.md +1 -1
  109. package/docs/api/interfaces/LabelProps.md +1 -1
  110. package/docs/api/interfaces/LoggerConfig.md +62 -0
  111. package/docs/api/interfaces/LoginFormProps.md +1 -1
  112. package/docs/api/interfaces/NavigationAccessRecord.md +1 -1
  113. package/docs/api/interfaces/NavigationContextType.md +1 -1
  114. package/docs/api/interfaces/NavigationGuardProps.md +1 -1
  115. package/docs/api/interfaces/NavigationItem.md +1 -1
  116. package/docs/api/interfaces/NavigationMenuProps.md +1 -1
  117. package/docs/api/interfaces/NavigationProviderProps.md +1 -1
  118. package/docs/api/interfaces/Organisation.md +1 -1
  119. package/docs/api/interfaces/OrganisationContextType.md +1 -1
  120. package/docs/api/interfaces/OrganisationMembership.md +1 -1
  121. package/docs/api/interfaces/OrganisationProviderProps.md +1 -1
  122. package/docs/api/interfaces/OrganisationSecurityError.md +1 -1
  123. package/docs/api/interfaces/PaceAppLayoutProps.md +36 -23
  124. package/docs/api/interfaces/PaceLoginPageProps.md +1 -1
  125. package/docs/api/interfaces/PageAccessRecord.md +1 -1
  126. package/docs/api/interfaces/PagePermissionContextType.md +1 -1
  127. package/docs/api/interfaces/PagePermissionGuardProps.md +11 -11
  128. package/docs/api/interfaces/PagePermissionProviderProps.md +1 -1
  129. package/docs/api/interfaces/PaletteData.md +1 -1
  130. package/docs/api/interfaces/PermissionEnforcerProps.md +1 -1
  131. package/docs/api/interfaces/ProgressProps.md +1 -1
  132. package/docs/api/interfaces/ProtectedRouteProps.md +1 -1
  133. package/docs/api/interfaces/PublicPageFooterProps.md +1 -1
  134. package/docs/api/interfaces/PublicPageHeaderProps.md +1 -1
  135. package/docs/api/interfaces/PublicPageLayoutProps.md +1 -1
  136. package/docs/api/interfaces/QuickFix.md +52 -0
  137. package/docs/api/interfaces/RBACAccessValidateParams.md +1 -1
  138. package/docs/api/interfaces/RBACAccessValidateResult.md +1 -1
  139. package/docs/api/interfaces/RBACAuditLogParams.md +1 -1
  140. package/docs/api/interfaces/RBACAuditLogResult.md +1 -1
  141. package/docs/api/interfaces/RBACConfig.md +4 -4
  142. package/docs/api/interfaces/RBACContext.md +1 -1
  143. package/docs/api/interfaces/RBACLogger.md +1 -1
  144. package/docs/api/interfaces/RBACPageAccessCheckParams.md +1 -1
  145. package/docs/api/interfaces/RBACPermissionCheckParams.md +1 -1
  146. package/docs/api/interfaces/RBACPermissionCheckResult.md +1 -1
  147. package/docs/api/interfaces/RBACPermissionsGetParams.md +1 -1
  148. package/docs/api/interfaces/RBACPermissionsGetResult.md +1 -1
  149. package/docs/api/interfaces/RBACResult.md +1 -1
  150. package/docs/api/interfaces/RBACRoleGrantParams.md +1 -1
  151. package/docs/api/interfaces/RBACRoleGrantResult.md +1 -1
  152. package/docs/api/interfaces/RBACRoleRevokeParams.md +1 -1
  153. package/docs/api/interfaces/RBACRoleRevokeResult.md +1 -1
  154. package/docs/api/interfaces/RBACRoleValidateParams.md +1 -1
  155. package/docs/api/interfaces/RBACRoleValidateResult.md +1 -1
  156. package/docs/api/interfaces/RBACRolesListParams.md +1 -1
  157. package/docs/api/interfaces/RBACRolesListResult.md +1 -1
  158. package/docs/api/interfaces/RBACSessionTrackParams.md +1 -1
  159. package/docs/api/interfaces/RBACSessionTrackResult.md +1 -1
  160. package/docs/api/interfaces/ResourcePermissions.md +1 -1
  161. package/docs/api/interfaces/RevokeEventAppRoleParams.md +7 -7
  162. package/docs/api/interfaces/RoleBasedRouterContextType.md +1 -1
  163. package/docs/api/interfaces/RoleBasedRouterProps.md +1 -1
  164. package/docs/api/interfaces/RoleManagementResult.md +5 -5
  165. package/docs/api/interfaces/RouteAccessRecord.md +1 -1
  166. package/docs/api/interfaces/RouteConfig.md +1 -1
  167. package/docs/api/interfaces/RuntimeComplianceResult.md +55 -0
  168. package/docs/api/interfaces/SecureDataContextType.md +1 -1
  169. package/docs/api/interfaces/SecureDataProviderProps.md +1 -1
  170. package/docs/api/interfaces/SessionRestorationLoaderProps.md +1 -1
  171. package/docs/api/interfaces/SetupIssue.md +41 -0
  172. package/docs/api/interfaces/StorageConfig.md +1 -1
  173. package/docs/api/interfaces/StorageFileInfo.md +1 -1
  174. package/docs/api/interfaces/StorageFileMetadata.md +1 -1
  175. package/docs/api/interfaces/StorageListOptions.md +1 -1
  176. package/docs/api/interfaces/StorageListResult.md +1 -1
  177. package/docs/api/interfaces/StorageUploadOptions.md +1 -1
  178. package/docs/api/interfaces/StorageUploadResult.md +1 -1
  179. package/docs/api/interfaces/StorageUrlOptions.md +1 -1
  180. package/docs/api/interfaces/StyleImport.md +1 -1
  181. package/docs/api/interfaces/SwitchProps.md +1 -1
  182. package/docs/api/interfaces/TabsContentProps.md +1 -1
  183. package/docs/api/interfaces/TabsListProps.md +1 -1
  184. package/docs/api/interfaces/TabsProps.md +1 -1
  185. package/docs/api/interfaces/TabsTriggerProps.md +1 -1
  186. package/docs/api/interfaces/TextareaProps.md +1 -1
  187. package/docs/api/interfaces/ToastActionElement.md +1 -1
  188. package/docs/api/interfaces/ToastProps.md +1 -1
  189. package/docs/api/interfaces/UnifiedAuthContextType.md +1 -1
  190. package/docs/api/interfaces/UnifiedAuthProviderProps.md +1 -1
  191. package/docs/api/interfaces/UseFormDialogOptions.md +62 -0
  192. package/docs/api/interfaces/UseFormDialogReturn.md +117 -0
  193. package/docs/api/interfaces/UseInactivityTrackerOptions.md +1 -1
  194. package/docs/api/interfaces/UseInactivityTrackerReturn.md +1 -1
  195. package/docs/api/interfaces/UsePublicEventLogoOptions.md +2 -2
  196. package/docs/api/interfaces/UsePublicEventLogoReturn.md +1 -1
  197. package/docs/api/interfaces/UsePublicEventOptions.md +1 -1
  198. package/docs/api/interfaces/UsePublicEventReturn.md +1 -1
  199. package/docs/api/interfaces/UsePublicFileDisplayOptions.md +2 -2
  200. package/docs/api/interfaces/UsePublicFileDisplayReturn.md +1 -1
  201. package/docs/api/interfaces/UsePublicRouteParamsReturn.md +1 -1
  202. package/docs/api/interfaces/UseResolvedScopeOptions.md +2 -2
  203. package/docs/api/interfaces/UseResolvedScopeReturn.md +1 -1
  204. package/docs/api/interfaces/UseResourcePermissionsOptions.md +1 -1
  205. package/docs/api/interfaces/UserEventAccess.md +1 -1
  206. package/docs/api/interfaces/UserMenuProps.md +1 -1
  207. package/docs/api/interfaces/UserProfile.md +1 -1
  208. package/docs/api/modules.md +738 -42
  209. package/docs/api-reference/hooks.md +111 -0
  210. package/docs/api-reference/rpc-functions.md +1 -1
  211. package/docs/api-reference/utilities.md +184 -0
  212. package/docs/getting-started/installation-guide.md +75 -16
  213. package/docs/getting-started/quick-start.md +61 -11
  214. package/docs/implementation-guides/authentication.md +88 -12
  215. package/docs/implementation-guides/file-reference-system.md +2 -1
  216. package/docs/implementation-guides/file-upload-storage.md +21 -0
  217. package/docs/rbac/README.md +1 -0
  218. package/docs/rbac/compliance/compliance-guide.md +544 -0
  219. package/docs/rbac/getting-started.md +158 -33
  220. package/docs/standards/pace-core-compliance.md +432 -0
  221. package/eslint-config-pace-core.cjs +93 -0
  222. package/package.json +15 -3
  223. package/scripts/analyze-bundle.js +232 -0
  224. package/scripts/build-css.js +56 -0
  225. package/scripts/build-docs-incremental.js +1015 -0
  226. package/scripts/check-pace-core-compliance.cjs +2353 -0
  227. package/scripts/generate-docs.js +157 -0
  228. package/scripts/setup-build-cache.js +73 -0
  229. package/scripts/utils/command-runner.js +131 -0
  230. package/scripts/utils/env.js +33 -0
  231. package/scripts/utils/index.js +10 -0
  232. package/scripts/utils/logger.js +88 -0
  233. package/scripts/utils/path-helpers.js +37 -0
  234. package/scripts/validate-formats.js +133 -0
  235. package/scripts/validate-master.js +155 -0
  236. package/scripts/validate-pre-publish.js +140 -0
  237. package/scripts/validate-theme.js +142 -0
  238. package/src/components/Calendar/Calendar.tsx +8 -1
  239. package/src/components/Card/Card.tsx +47 -8
  240. package/src/components/DatePickerWithTimezone/DatePickerWithTimezone.test.tsx +314 -0
  241. package/src/components/DatePickerWithTimezone/DatePickerWithTimezone.tsx +126 -0
  242. package/src/components/DatePickerWithTimezone/README.md +135 -0
  243. package/src/components/DatePickerWithTimezone/index.ts +10 -0
  244. package/src/components/DateTimeField/DateTimeField.test.tsx +358 -0
  245. package/src/components/DateTimeField/DateTimeField.tsx +232 -0
  246. package/src/components/DateTimeField/README.md +148 -0
  247. package/src/components/DateTimeField/index.ts +10 -0
  248. package/src/components/FileUpload/FileUpload.tsx +3 -0
  249. package/src/components/Header/Header.test.tsx +47 -18
  250. package/src/components/Header/Header.tsx +24 -6
  251. package/src/components/PaceAppLayout/PaceAppLayout.tsx +29 -20
  252. package/src/components/PaceAppLayout/README.md +9 -0
  253. package/src/components/PaceLoginPage/PaceLoginPage.tsx +1 -1
  254. package/src/components/ProtectedRoute/ProtectedRoute.test.tsx +37 -8
  255. package/src/components/ProtectedRoute/ProtectedRoute.tsx +12 -4
  256. package/src/components/index.ts +8 -0
  257. package/src/eslint-rules/pace-core-compliance.cjs +406 -0
  258. package/src/eslint-rules/pace-core-compliance.js +640 -0
  259. package/src/hooks/__tests__/useFormDialog.test.ts +478 -0
  260. package/src/hooks/index.ts +2 -0
  261. package/src/hooks/useFileReference.test.ts +1 -0
  262. package/src/hooks/useFormDialog.ts +147 -0
  263. package/src/index.ts +27 -0
  264. package/src/providers/services/OrganisationServiceProvider.tsx +6 -5
  265. package/src/providers/services/UnifiedAuthProvider.tsx +24 -3
  266. package/src/rbac/__tests__/scenarios.user-role.test.tsx +3 -0
  267. package/src/rbac/compliance/database-validator.ts +165 -0
  268. package/src/rbac/compliance/index.ts +38 -0
  269. package/src/rbac/compliance/quick-fix-suggestions.ts +209 -0
  270. package/src/rbac/compliance/runtime-compliance.ts +77 -0
  271. package/src/rbac/compliance/setup-validator.ts +131 -0
  272. package/src/rbac/components/PagePermissionGuard.tsx +8 -64
  273. package/src/rbac/components/__tests__/PagePermissionGuard.test.tsx +35 -21
  274. package/src/rbac/docs/event-based-apps.md +285 -0
  275. package/src/rbac/errors.ts +11 -0
  276. package/src/rbac/hooks/useRoleManagement.ts +292 -12
  277. package/src/rbac/index.ts +30 -0
  278. package/src/services/OrganisationService.ts +4 -0
  279. package/src/types/file-reference.ts +6 -0
  280. package/src/utils/__tests__/timezone.test.ts +345 -0
  281. package/src/utils/file-reference/__tests__/file-reference.test.ts +2 -0
  282. package/src/utils/file-reference/index.ts +1 -0
  283. package/src/utils/formatting/formatDateTimeTimezone.test.ts +167 -0
  284. package/src/utils/formatting/formatting.ts +179 -0
  285. package/src/utils/index.ts +27 -1
  286. package/src/utils/location/index.ts +16 -0
  287. package/src/utils/location/location.test.ts +286 -0
  288. package/src/utils/location/location.ts +175 -0
  289. package/src/utils/timezone/index.ts +17 -0
  290. package/src/utils/timezone/timezone.test.ts +349 -0
  291. package/src/utils/timezone/timezone.ts +281 -0
  292. package/dist/chunk-CSOFYHAG.js.map +0 -1
  293. package/dist/chunk-FUEYYMX5.js.map +0 -1
  294. package/dist/chunk-HKIT6O7W.js +0 -198
  295. package/dist/chunk-HKIT6O7W.js.map +0 -1
  296. package/dist/chunk-KUEN3HFB.js +0 -94
  297. package/dist/chunk-KUEN3HFB.js.map +0 -1
  298. package/dist/chunk-M7W4CP3M.js.map +0 -1
  299. package/dist/chunk-PWAHJW4G.js.map +0 -1
  300. package/dist/chunk-QETLRQI6.js.map +0 -1
  301. package/dist/chunk-UHNYIBXL.js.map +0 -1
  302. package/dist/formatting-5wETwiGF.d.ts +0 -162
  303. /package/dist/{DataTable-QAB34V6K.js.map → DataTable-IX2NBUTP.js.map} +0 -0
  304. /package/dist/{UnifiedAuthProvider-7F6T4B6K.js.map → UnifiedAuthProvider-A4BCQRJY.js.map} +0 -0
  305. /package/dist/{api-ROMBCNKU.js.map → api-BMFCXVQX.js.map} +0 -0
  306. /package/dist/{chunk-W22JP75J.js.map → chunk-STTZQK2I.js.map} +0 -0
  307. /package/dist/{chunk-QCDXODCA.js.map → chunk-XAUHJD3L.js.map} +0 -0
@@ -0,0 +1,640 @@
1
+ /**
2
+ * ESLint Rules for pace-core Compliance Enforcement
3
+ * @package @jmruthers/pace-core
4
+ * @module ESLintRules/pace-core-compliance
5
+ * @since 1.0.0
6
+ *
7
+ * This module provides ESLint rules to enforce pace-core usage patterns
8
+ * and prevent consuming apps from creating local alternatives.
9
+ */
10
+
11
+ const fs = require('fs');
12
+ const path = require('path');
13
+
14
+ // Load manifest data
15
+ let manifestData = null;
16
+ try {
17
+ const manifestPath = path.join(__dirname, '../../core-usage-manifest.json');
18
+ if (fs.existsSync(manifestPath)) {
19
+ manifestData = JSON.parse(fs.readFileSync(manifestPath, 'utf8'));
20
+ }
21
+ } catch (error) {
22
+ // If manifest can't be loaded, rules will use hardcoded defaults
23
+ console.warn('Warning: Could not load core-usage-manifest.json, using defaults');
24
+ }
25
+
26
+ // Get restricted imports from manifest or use defaults
27
+ const getRestrictedImports = () => {
28
+ if (manifestData && manifestData.restrictedImports) {
29
+ return manifestData.restrictedImports;
30
+ }
31
+ // Fallback defaults
32
+ return [
33
+ { module: '@radix-ui/react-avatar', reason: 'Use Avatar component from pace-core instead' },
34
+ { module: '@radix-ui/react-checkbox', reason: 'Use Checkbox component from pace-core instead' },
35
+ { module: '@radix-ui/react-dialog', reason: 'Use Dialog component from pace-core instead' },
36
+ { module: '@radix-ui/react-label', reason: 'Use Label component from pace-core instead' },
37
+ { module: '@radix-ui/react-progress', reason: 'Use Progress component from pace-core instead' },
38
+ { module: '@radix-ui/react-slot', reason: 'Use Button component from pace-core which handles slot composition' },
39
+ { module: '@radix-ui/react-switch', reason: 'Use Switch component from pace-core instead' },
40
+ { module: '@radix-ui/react-tabs', reason: 'Use Tabs component from pace-core instead' },
41
+ { module: '@radix-ui/react-toast', reason: 'Use Toast component and useToast hook from pace-core instead' },
42
+ { module: '@radix-ui/react-tooltip', reason: 'Use Tooltip component from pace-core instead' },
43
+ { module: 'react-day-picker', reason: 'Use Calendar component from pace-core instead' },
44
+ { module: '@tanstack/react-table', reason: 'Use DataTable component and related hooks from pace-core instead. DataTable wraps and standardizes table functionality' },
45
+ { module: 'react-hook-form', reason: 'Use Form component and useZodForm hook from pace-core instead' },
46
+ { module: 'zod', reason: 'Use validation utilities and schemas from pace-core instead. pace-core provides standardized validation helpers' }
47
+ ];
48
+ };
49
+
50
+ // Get pace-core components from manifest or use defaults
51
+ const getPaceCoreComponents = () => {
52
+ if (manifestData && manifestData.components) {
53
+ return manifestData.components;
54
+ }
55
+ return ['Button', 'Card', 'Dialog', 'Input', 'Form', 'Select', 'Alert', 'Badge', 'Checkbox', 'Switch', 'Textarea', 'Label', 'Table', 'DataTable', 'Toast', 'Tooltip', 'Tabs', 'Calendar', 'Avatar', 'Progress'];
56
+ };
57
+
58
+ // Get pace-core hooks from manifest or use defaults
59
+ const getPaceCoreHooks = () => {
60
+ if (manifestData && manifestData.hooks) {
61
+ return manifestData.hooks;
62
+ }
63
+ return ['useToast', 'useDebounce', 'useUnifiedAuth', 'useEvents', 'useOrganisations', 'useFileReference', 'useStorage', 'useZodForm', 'useRBAC', 'usePermissions'];
64
+ };
65
+
66
+ // Get pace-core utils from manifest or use defaults
67
+ const getPaceCoreUtils = () => {
68
+ if (manifestData && manifestData.utils) {
69
+ return manifestData.utils;
70
+ }
71
+ return ['formatDate', 'formatCurrency', 'formatNumber', 'formatTime', 'formatDateTime', 'cn', 'validateUserInput', 'sanitizeUserInput', 'hasPermission', 'getAppConfig'];
72
+ };
73
+
74
+ module.exports = {
75
+ rules: {
76
+ /**
77
+ * Block direct imports of libraries wrapped by pace-core
78
+ */
79
+ 'no-restricted-imports': {
80
+ meta: {
81
+ type: 'problem',
82
+ docs: {
83
+ description: 'Disallow direct imports of libraries that pace-core wraps',
84
+ category: 'Best Practices',
85
+ recommended: true
86
+ },
87
+ fixable: 'code',
88
+ messages: {
89
+ restrictedImport: '{{message}} Import from {{alternative}} instead.',
90
+ restrictedImportWithReason: '{{message}} {{reason}}'
91
+ }
92
+ },
93
+ create(context) {
94
+ const restrictedImports = getRestrictedImports();
95
+ const restrictedModules = restrictedImports.map(imp => imp.module);
96
+
97
+ // Also catch @radix-ui/* patterns
98
+ const radixPattern = /^@radix-ui\//;
99
+
100
+ return {
101
+ ImportDeclaration(node) {
102
+ const importSource = node.source.value;
103
+
104
+ // Check exact matches
105
+ const restricted = restrictedImports.find(imp => imp.module === importSource);
106
+ if (restricted) {
107
+ context.report({
108
+ node: node.source,
109
+ messageId: 'restrictedImportWithReason',
110
+ data: {
111
+ message: `Direct import of '${importSource}' is not allowed.`,
112
+ reason: restricted.reason
113
+ },
114
+ suggest: [{
115
+ desc: `Use pace-core alternative: ${restricted.reason}`,
116
+ fix(fixer) {
117
+ // Suggest importing from pace-core instead
118
+ const paceCoreAlternative = getPaceCoreAlternative(importSource);
119
+ if (paceCoreAlternative) {
120
+ return fixer.replaceText(
121
+ node.source,
122
+ `'@jmruthers/pace-core${paceCoreAlternative}'`
123
+ );
124
+ }
125
+ return null;
126
+ }
127
+ }]
128
+ });
129
+ return;
130
+ }
131
+
132
+ // Check @radix-ui/* pattern
133
+ if (radixPattern.test(importSource) && !restrictedModules.includes(importSource)) {
134
+ context.report({
135
+ node: node.source,
136
+ messageId: 'restrictedImport',
137
+ data: {
138
+ message: `Direct import of '${importSource}' is not allowed.`,
139
+ alternative: '@jmruthers/pace-core'
140
+ },
141
+ suggest: [{
142
+ desc: 'Use pace-core component instead',
143
+ fix(fixer) {
144
+ return fixer.replaceText(
145
+ node.source,
146
+ "'@jmruthers/pace-core'"
147
+ );
148
+ }
149
+ }]
150
+ });
151
+ }
152
+ }
153
+ };
154
+ }
155
+ },
156
+
157
+ /**
158
+ * Prefer pace-core components over native HTML elements or custom implementations
159
+ */
160
+ 'prefer-pace-core-components': {
161
+ meta: {
162
+ type: 'suggestion',
163
+ docs: {
164
+ description: 'Suggest using pace-core components instead of native HTML elements',
165
+ category: 'Best Practices',
166
+ recommended: true
167
+ },
168
+ messages: {
169
+ preferButton: "Use 'Button' component from '@jmruthers/pace-core' instead of <button>",
170
+ preferInput: "Use 'Input' component from '@jmruthers/pace-core' instead of <input>",
171
+ preferTextarea: "Use 'Textarea' component from '@jmruthers/pace-core' instead of <textarea>",
172
+ preferLabel: "Use 'Label' component from '@jmruthers/pace-core' instead of <label>",
173
+ preferForm: "Use 'Form' component from '@jmruthers/pace-core' instead of custom form implementation"
174
+ }
175
+ },
176
+ create(context) {
177
+ const paceCoreComponents = getPaceCoreComponents();
178
+
179
+ return {
180
+ JSXOpeningElement(node) {
181
+ const elementName = node.name.name;
182
+
183
+ if (!elementName) return;
184
+
185
+ // Check for native HTML elements that have pace-core alternatives
186
+ const nativeToPaceCore = {
187
+ 'button': 'Button',
188
+ 'input': 'Input',
189
+ 'textarea': 'Textarea',
190
+ 'label': 'Label'
191
+ };
192
+
193
+ if (nativeToPaceCore[elementName.toLowerCase()]) {
194
+ const paceCoreComponent = nativeToPaceCore[elementName.toLowerCase()];
195
+ if (paceCoreComponents.includes(paceCoreComponent)) {
196
+ context.report({
197
+ node,
198
+ messageId: `prefer${paceCoreComponent}`,
199
+ suggest: [{
200
+ desc: `Import and use ${paceCoreComponent} from pace-core`,
201
+ fix(fixer) {
202
+ // This is a complex fix, so we'll just report
203
+ return null;
204
+ }
205
+ }]
206
+ });
207
+ }
208
+ }
209
+ }
210
+ };
211
+ }
212
+ },
213
+
214
+ /**
215
+ * Detect custom hooks that duplicate pace-core functionality
216
+ */
217
+ 'prefer-pace-core-hooks': {
218
+ meta: {
219
+ type: 'suggestion',
220
+ docs: {
221
+ description: 'Suggest using pace-core hooks instead of custom implementations',
222
+ category: 'Best Practices',
223
+ recommended: true
224
+ },
225
+ messages: {
226
+ preferPaceCoreHook: "Consider using '{{hook}}' from '@jmruthers/pace-core' instead of custom hook '{{customHook}}'"
227
+ }
228
+ },
229
+ create(context) {
230
+ const paceCoreHooks = getPaceCoreHooks();
231
+ const hookPatterns = {
232
+ 'useToast': ['useToast', 'useNotification', 'useSnackbar'],
233
+ 'useDebounce': ['useDebounce', 'useDebounced'],
234
+ 'useAuth': ['useAuth', 'useAuthentication', 'useUser'],
235
+ 'useFile': ['useFile', 'useFileUpload', 'useFileReference'],
236
+ 'useForm': ['useForm', 'useZodForm'],
237
+ 'useTable': ['useTable', 'useDataTable']
238
+ };
239
+
240
+ return {
241
+ FunctionDeclaration(node) {
242
+ const functionName = node.id?.name;
243
+ if (!functionName || !functionName.startsWith('use')) return;
244
+
245
+ // Check if this looks like a hook that pace-core provides
246
+ for (const [paceCoreHook, patterns] of Object.entries(hookPatterns)) {
247
+ if (paceCoreHooks.includes(paceCoreHook)) {
248
+ for (const pattern of patterns) {
249
+ if (functionName.toLowerCase().includes(pattern.toLowerCase().replace('use', ''))) {
250
+ context.report({
251
+ node: node.id,
252
+ messageId: 'preferPaceCoreHook',
253
+ data: {
254
+ hook: paceCoreHook,
255
+ customHook: functionName
256
+ }
257
+ });
258
+ return;
259
+ }
260
+ }
261
+ }
262
+ }
263
+ }
264
+ };
265
+ }
266
+ },
267
+
268
+ /**
269
+ * Detect utility functions that duplicate pace-core functionality
270
+ */
271
+ 'prefer-pace-core-utils': {
272
+ meta: {
273
+ type: 'suggestion',
274
+ docs: {
275
+ description: 'Suggest using pace-core utilities instead of custom implementations',
276
+ category: 'Best Practices',
277
+ recommended: true
278
+ },
279
+ messages: {
280
+ preferPaceCoreUtil: "Consider using '{{util}}' from '@jmruthers/pace-core' instead of custom function '{{customUtil}}'"
281
+ }
282
+ },
283
+ create(context) {
284
+ const paceCoreUtils = getPaceCoreUtils();
285
+ const utilPatterns = {
286
+ 'formatDate': ['formatDate', 'formatDateTime', 'dateFormat'],
287
+ 'formatCurrency': ['formatCurrency', 'formatMoney', 'currencyFormat'],
288
+ 'formatNumber': ['formatNumber', 'numberFormat'],
289
+ 'cn': ['cn', 'classNames', 'clsx', 'mergeClasses'],
290
+ 'validateUserInput': ['validate', 'validateInput', 'validateUser'],
291
+ 'sanitizeUserInput': ['sanitize', 'sanitizeInput', 'sanitizeUser']
292
+ };
293
+
294
+ return {
295
+ FunctionDeclaration(node) {
296
+ const functionName = node.id?.name;
297
+ if (!functionName) return;
298
+
299
+ // Check if this looks like a util that pace-core provides
300
+ for (const [paceCoreUtil, patterns] of Object.entries(utilPatterns)) {
301
+ if (paceCoreUtils.includes(paceCoreUtil)) {
302
+ for (const pattern of patterns) {
303
+ if (functionName.toLowerCase().includes(pattern.toLowerCase())) {
304
+ context.report({
305
+ node: node.id,
306
+ messageId: 'preferPaceCoreUtil',
307
+ data: {
308
+ util: paceCoreUtil,
309
+ customUtil: functionName
310
+ }
311
+ });
312
+ return;
313
+ }
314
+ }
315
+ }
316
+ }
317
+ }
318
+ };
319
+ }
320
+ },
321
+
322
+ /**
323
+ * Disallow custom auth/rbac/permission implementations
324
+ */
325
+ 'no-custom-auth-code': {
326
+ meta: {
327
+ type: 'problem',
328
+ docs: {
329
+ description: 'Disallow custom auth/rbac/permission implementations. Must use pace-core exclusively.',
330
+ category: 'Best Practices',
331
+ recommended: true
332
+ },
333
+ messages: {
334
+ customAuthCode: "Custom {{type}} '{{name}}' detected. Use pace-core's {{name}} from '@jmruthers/pace-core' instead."
335
+ }
336
+ },
337
+ create(context) {
338
+ const authRbacPatterns = [
339
+ { name: 'useAuth', type: 'hook' },
340
+ { name: 'useLogin', type: 'hook' },
341
+ { name: 'useLogout', type: 'hook' },
342
+ { name: 'useSession', type: 'hook' },
343
+ { name: 'useUser', type: 'hook' },
344
+ { name: 'useAuthentication', type: 'hook' },
345
+ { name: 'usePermissions', type: 'hook' },
346
+ { name: 'useCan', type: 'hook' },
347
+ { name: 'useAccessLevel', type: 'hook' },
348
+ { name: 'useRole', type: 'hook' },
349
+ { name: 'PermissionGuard', type: 'component' },
350
+ { name: 'AuthGuard', type: 'component' },
351
+ { name: 'RoleGuard', type: 'component' },
352
+ { name: 'AccessGuard', type: 'component' },
353
+ { name: 'checkPermission', type: 'util' },
354
+ { name: 'hasPermission', type: 'util' },
355
+ { name: 'hasAccess', type: 'util' },
356
+ { name: 'canAccess', type: 'util' },
357
+ { name: 'isPermitted', type: 'util' }
358
+ ];
359
+
360
+ let hasPaceCoreImport = false;
361
+
362
+ return {
363
+ ImportDeclaration(node) {
364
+ const importSource = node.source.value;
365
+ if (importSource === '@jmruthers/pace-core' ||
366
+ importSource.startsWith('@jmruthers/pace-core/')) {
367
+ hasPaceCoreImport = true;
368
+ }
369
+ },
370
+ FunctionDeclaration(node) {
371
+ const functionName = node.id?.name;
372
+ if (!functionName) return;
373
+
374
+ const pattern = authRbacPatterns.find(p => p.name === functionName);
375
+ if (pattern && !hasPaceCoreImport) {
376
+ context.report({
377
+ node: node.id,
378
+ messageId: 'customAuthCode',
379
+ data: {
380
+ name: functionName,
381
+ type: pattern.type
382
+ }
383
+ });
384
+ }
385
+ },
386
+ VariableDeclarator(node) {
387
+ if (node.id.type === 'Identifier') {
388
+ const varName = node.id.name;
389
+ const pattern = authRbacPatterns.find(p => p.name === varName);
390
+ if (pattern && !hasPaceCoreImport) {
391
+ // Check if it's a function expression
392
+ if (node.init && (node.init.type === 'FunctionExpression' || node.init.type === 'ArrowFunctionExpression')) {
393
+ context.report({
394
+ node: node.id,
395
+ messageId: 'customAuthCode',
396
+ data: {
397
+ name: varName,
398
+ type: pattern.type
399
+ }
400
+ });
401
+ }
402
+ }
403
+ }
404
+ }
405
+ };
406
+ }
407
+ },
408
+
409
+ /**
410
+ * Disallow multiple Supabase client configurations
411
+ */
412
+ 'no-duplicate-supabase-config': {
413
+ meta: {
414
+ type: 'problem',
415
+ docs: {
416
+ description: 'Disallow multiple Supabase client configurations. Consolidate to a single configuration.',
417
+ category: 'Best Practices',
418
+ recommended: true
419
+ },
420
+ messages: {
421
+ duplicateConfig: 'Multiple Supabase client instantiations found. Consolidate to a single client configuration.'
422
+ }
423
+ },
424
+ create(context) {
425
+ let createClientCount = 0;
426
+
427
+ return {
428
+ CallExpression(node) {
429
+ if (node.callee.type === 'Identifier' && node.callee.name === 'createClient') {
430
+ createClientCount++;
431
+ if (createClientCount > 1) {
432
+ context.report({
433
+ node,
434
+ messageId: 'duplicateConfig'
435
+ });
436
+ }
437
+ }
438
+ }
439
+ };
440
+ }
441
+ },
442
+
443
+ /**
444
+ * Require PagePermissionGuard on all pages/routes
445
+ */
446
+ 'require-page-permission-guard': {
447
+ meta: {
448
+ type: 'problem',
449
+ docs: {
450
+ description: 'Require PagePermissionGuard on all pages/routes for proper RBAC enforcement.',
451
+ category: 'Best Practices',
452
+ recommended: true
453
+ },
454
+ messages: {
455
+ missingGuard: 'Route/page found without PagePermissionGuard. All routes should be protected with PagePermissionGuard from pace-core.'
456
+ }
457
+ },
458
+ create(context) {
459
+ let hasRoute = false;
460
+ let hasPagePermissionGuard = false;
461
+ let hasPaceCoreRBACImport = false;
462
+
463
+ return {
464
+ ImportDeclaration(node) {
465
+ const importSource = node.source.value;
466
+ if (importSource === '@jmruthers/pace-core/rbac' ||
467
+ importSource.startsWith('@jmruthers/pace-core/rbac/')) {
468
+ hasPaceCoreRBACImport = true;
469
+ }
470
+ if (node.specifiers.some(spec =>
471
+ spec.type === 'ImportSpecifier' && spec.imported.name === 'PagePermissionGuard'
472
+ )) {
473
+ hasPagePermissionGuard = true;
474
+ }
475
+ },
476
+ JSXOpeningElement(node) {
477
+ if (node.name.name === 'Route') {
478
+ hasRoute = true;
479
+ }
480
+ if (node.name.name === 'PagePermissionGuard') {
481
+ hasPagePermissionGuard = true;
482
+ }
483
+ },
484
+ CallExpression(node) {
485
+ if (node.callee.type === 'Identifier') {
486
+ if (node.callee.name === 'createBrowserRouter' ||
487
+ node.callee.name === 'createRoutesFromElements') {
488
+ hasRoute = true;
489
+ }
490
+ }
491
+ },
492
+ 'Program:exit'(node) {
493
+ if (hasRoute && !hasPagePermissionGuard && !hasPaceCoreRBACImport) {
494
+ context.report({
495
+ node,
496
+ messageId: 'missingGuard'
497
+ });
498
+ }
499
+ }
500
+ };
501
+ }
502
+ },
503
+
504
+ /**
505
+ * Disallow direct Supabase auth usage
506
+ */
507
+ 'no-direct-supabase-auth': {
508
+ meta: {
509
+ type: 'problem',
510
+ docs: {
511
+ description: 'Disallow direct Supabase auth usage. Use UnifiedAuthProvider and useUnifiedAuth from pace-core instead.',
512
+ category: 'Best Practices',
513
+ recommended: true
514
+ },
515
+ messages: {
516
+ directAuth: 'Direct Supabase auth usage detected. Use UnifiedAuthProvider and useUnifiedAuth from pace-core instead.'
517
+ }
518
+ },
519
+ create(context) {
520
+ let hasUnifiedAuth = false;
521
+
522
+ return {
523
+ ImportDeclaration(node) {
524
+ const importSource = node.source.value;
525
+ if (importSource === '@jmruthers/pace-core' ||
526
+ importSource.startsWith('@jmruthers/pace-core/')) {
527
+ if (node.specifiers.some(spec =>
528
+ (spec.type === 'ImportSpecifier' &&
529
+ (spec.imported.name === 'UnifiedAuthProvider' || spec.imported.name === 'useUnifiedAuth'))
530
+ )) {
531
+ hasUnifiedAuth = true;
532
+ }
533
+ }
534
+ },
535
+ MemberExpression(node) {
536
+ if (node.property &&
537
+ (node.property.name === 'signIn' ||
538
+ node.property.name === 'signUp' ||
539
+ node.property.name === 'signOut' ||
540
+ node.property.name === 'getSession' ||
541
+ node.property.name === 'getUser')) {
542
+ if (node.object.type === 'MemberExpression' &&
543
+ node.object.property &&
544
+ node.object.property.name === 'auth') {
545
+ if (!hasUnifiedAuth) {
546
+ context.report({
547
+ node,
548
+ messageId: 'directAuth'
549
+ });
550
+ }
551
+ }
552
+ }
553
+ }
554
+ };
555
+ }
556
+ },
557
+
558
+ /**
559
+ * Detect component files with names matching pace-core components
560
+ */
561
+ 'no-local-component-duplication': {
562
+ meta: {
563
+ type: 'problem',
564
+ docs: {
565
+ description: 'Disallow local components with names matching pace-core components',
566
+ category: 'Best Practices',
567
+ recommended: true
568
+ },
569
+ messages: {
570
+ duplicateComponent: "Component '{{componentName}}' conflicts with pace-core component. Use '@jmruthers/pace-core' instead of creating a local version."
571
+ }
572
+ },
573
+ create(context) {
574
+ const paceCoreComponents = getPaceCoreComponents();
575
+ const filename = context.getFilename();
576
+
577
+ // Only check component files (components/, src/components/, etc.)
578
+ if (!filename.match(/(components|Components)\//)) {
579
+ return {};
580
+ }
581
+
582
+ // Extract component name from filename
583
+ const basename = path.basename(filename, path.extname(filename));
584
+ const componentName = basename.replace(/\.(test|spec)$/, '');
585
+
586
+ return {
587
+ Program(node) {
588
+ // Check if this file exports a component with a name matching pace-core
589
+ if (paceCoreComponents.includes(componentName)) {
590
+ // Check if file exports this component
591
+ const hasExport = node.body.some(stmt => {
592
+ if (stmt.type === 'ExportNamedDeclaration') {
593
+ return stmt.declaration?.id?.name === componentName ||
594
+ stmt.specifiers?.some(spec => spec.exported.name === componentName);
595
+ }
596
+ if (stmt.type === 'ExportDefaultDeclaration') {
597
+ return stmt.declaration?.id?.name === componentName ||
598
+ stmt.declaration?.name === componentName;
599
+ }
600
+ return false;
601
+ });
602
+
603
+ if (hasExport) {
604
+ context.report({
605
+ node,
606
+ messageId: 'duplicateComponent',
607
+ data: {
608
+ componentName
609
+ }
610
+ });
611
+ }
612
+ }
613
+ }
614
+ };
615
+ }
616
+ }
617
+ }
618
+ };
619
+
620
+ // Helper function to get pace-core alternative for restricted imports
621
+ function getPaceCoreAlternative(importSource) {
622
+ const alternatives = {
623
+ '@radix-ui/react-avatar': '/components',
624
+ '@radix-ui/react-checkbox': '/components',
625
+ '@radix-ui/react-dialog': '/components',
626
+ '@radix-ui/react-label': '/components',
627
+ '@radix-ui/react-progress': '/components',
628
+ '@radix-ui/react-switch': '/components',
629
+ '@radix-ui/react-tabs': '/components',
630
+ '@radix-ui/react-toast': '/components',
631
+ '@radix-ui/react-tooltip': '/components',
632
+ 'react-day-picker': '/components',
633
+ '@tanstack/react-table': '/components',
634
+ 'react-hook-form': '/components',
635
+ 'zod': '/utils'
636
+ };
637
+
638
+ return alternatives[importSource] || '';
639
+ }
640
+