@jmruthers/pace-core 0.5.185 → 0.5.187

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 (300) hide show
  1. package/dist/{DataTable-Z9NLVJh0.d.ts → DataTable-IVYljGJ6.d.ts} +1 -1
  2. package/dist/{DataTable-IX2NBUTP.js → DataTable-K3RJRSOX.js} +7 -7
  3. package/dist/{PublicPageProvider-BABf6JCh.d.ts → PublicPageProvider-DrLDztHt.d.ts} +214 -107
  4. package/dist/{UnifiedAuthProvider-A4BCQRJY.js → UnifiedAuthProvider-B76OWOAT.js} +2 -2
  5. package/dist/{api-BMFCXVQX.js → api-YP7XD5L6.js} +3 -3
  6. package/dist/{audit-WRS3KJKI.js → audit-B5P6FFIR.js} +2 -2
  7. package/dist/{chunk-445GEP27.js → chunk-3IC5WCMO.js} +33 -8
  8. package/dist/chunk-3IC5WCMO.js.map +1 -0
  9. package/dist/{chunk-OKI34GZD.js → chunk-3NFNJOO7.js} +8 -8
  10. package/dist/chunk-3NFNJOO7.js.map +1 -0
  11. package/dist/{chunk-FSFQFJCU.js → chunk-63FOKYGO.js} +174 -6
  12. package/dist/chunk-63FOKYGO.js.map +1 -0
  13. package/dist/{chunk-MX3EIJGQ.js → chunk-C4OYJOV4.js} +631 -97
  14. package/dist/chunk-C4OYJOV4.js.map +1 -0
  15. package/dist/{chunk-HGPQUCBC.js → chunk-FMTK4XNN.js} +3 -3
  16. package/dist/{chunk-U6WNSFX5.js → chunk-HEHYGYOX.js} +279 -44
  17. package/dist/chunk-HEHYGYOX.js.map +1 -0
  18. package/dist/{chunk-XAUHJD3L.js → chunk-K2JGDXGU.js} +2 -2
  19. package/dist/{chunk-HC67NW5K.js → chunk-LBBUPSSC.js} +863 -552
  20. package/dist/chunk-LBBUPSSC.js.map +1 -0
  21. package/dist/{chunk-IXSNYUCT.js → chunk-SAUPYVLF.js} +1 -1
  22. package/dist/chunk-SAUPYVLF.js.map +1 -0
  23. package/dist/{chunk-AISXLWGZ.js → chunk-T6ZJVI3A.js} +27 -23
  24. package/dist/chunk-T6ZJVI3A.js.map +1 -0
  25. package/dist/{chunk-STTZQK2I.js → chunk-ULX5FYEM.js} +9 -7
  26. package/dist/chunk-ULX5FYEM.js.map +1 -0
  27. package/dist/{chunk-FXFJRTKI.js → chunk-WK2Y6TGA.js} +3 -3
  28. package/dist/chunk-WK2Y6TGA.js.map +1 -0
  29. package/dist/chunk-YHCN776L.js +447 -0
  30. package/dist/chunk-YHCN776L.js.map +1 -0
  31. package/dist/components.d.ts +4 -4
  32. package/dist/components.js +12 -10
  33. package/dist/components.js.map +1 -1
  34. package/dist/{database.generated-CBmg2950.d.ts → database.generated-DI89OQeI.d.ts} +63 -9
  35. package/dist/{file-reference-BjR39ktt.d.ts → file-reference-D037xOFK.d.ts} +3 -1
  36. package/dist/hooks.d.ts +265 -6
  37. package/dist/hooks.js +148 -49
  38. package/dist/hooks.js.map +1 -1
  39. package/dist/index.d.ts +25 -10
  40. package/dist/index.js +65 -30
  41. package/dist/index.js.map +1 -1
  42. package/dist/providers.js +1 -1
  43. package/dist/rbac/index.d.ts +125 -8
  44. package/dist/rbac/index.js +27 -7
  45. package/dist/{types-DUyCRSTj.d.ts → types-Bwgl--Xo.d.ts} +162 -1
  46. package/dist/types.d.ts +2 -2
  47. package/dist/types.js +1 -1
  48. package/dist/{usePublicRouteParams-CvnC3d-e.d.ts → usePublicRouteParams-CTDELQ7H.d.ts} +3 -3
  49. package/dist/utils.d.ts +214 -4
  50. package/dist/utils.js +22 -2
  51. package/dist/utils.js.map +1 -1
  52. package/docs/api/classes/ColumnFactory.md +1 -1
  53. package/docs/api/classes/ErrorBoundary.md +1 -1
  54. package/docs/api/classes/InvalidScopeError.md +1 -1
  55. package/docs/api/classes/Logger.md +1 -1
  56. package/docs/api/classes/MissingUserContextError.md +1 -1
  57. package/docs/api/classes/OrganisationContextRequiredError.md +1 -1
  58. package/docs/api/classes/PermissionDeniedError.md +1 -1
  59. package/docs/api/classes/RBACAuditManager.md +21 -17
  60. package/docs/api/classes/RBACCache.md +31 -23
  61. package/docs/api/classes/RBACEngine.md +6 -6
  62. package/docs/api/classes/RBACError.md +1 -1
  63. package/docs/api/classes/RBACNotInitializedError.md +1 -1
  64. package/docs/api/classes/SecureSupabaseClient.md +5 -5
  65. package/docs/api/classes/StorageUtils.md +1 -1
  66. package/docs/api/enums/FileCategory.md +1 -1
  67. package/docs/api/enums/LogLevel.md +1 -1
  68. package/docs/api/enums/RBACErrorCode.md +1 -1
  69. package/docs/api/enums/RPCFunction.md +1 -1
  70. package/docs/api/interfaces/AddressFieldProps.md +241 -0
  71. package/docs/api/interfaces/AddressFieldRef.md +94 -0
  72. package/docs/api/interfaces/AggregateConfig.md +1 -1
  73. package/docs/api/interfaces/AutocompleteOptions.md +75 -0
  74. package/docs/api/interfaces/BadgeProps.md +1 -1
  75. package/docs/api/interfaces/ButtonProps.md +1 -1
  76. package/docs/api/interfaces/CalendarProps.md +1 -1
  77. package/docs/api/interfaces/CardProps.md +1 -1
  78. package/docs/api/interfaces/ColorPalette.md +1 -1
  79. package/docs/api/interfaces/ColorShade.md +1 -1
  80. package/docs/api/interfaces/ComplianceResult.md +1 -1
  81. package/docs/api/interfaces/DataAccessRecord.md +1 -1
  82. package/docs/api/interfaces/DataRecord.md +1 -1
  83. package/docs/api/interfaces/DataTableAction.md +1 -1
  84. package/docs/api/interfaces/DataTableColumn.md +1 -1
  85. package/docs/api/interfaces/DataTableProps.md +1 -1
  86. package/docs/api/interfaces/DataTableToolbarButton.md +1 -1
  87. package/docs/api/interfaces/DatabaseComplianceResult.md +1 -1
  88. package/docs/api/interfaces/DatabaseIssue.md +1 -1
  89. package/docs/api/interfaces/EmptyStateConfig.md +1 -1
  90. package/docs/api/interfaces/EnhancedNavigationMenuProps.md +1 -1
  91. package/docs/api/interfaces/EventAppRoleData.md +1 -1
  92. package/docs/api/interfaces/ExportColumn.md +1 -1
  93. package/docs/api/interfaces/ExportOptions.md +1 -1
  94. package/docs/api/interfaces/FileDisplayProps.md +15 -15
  95. package/docs/api/interfaces/FileMetadata.md +1 -1
  96. package/docs/api/interfaces/FileReference.md +1 -1
  97. package/docs/api/interfaces/FileSizeLimits.md +1 -1
  98. package/docs/api/interfaces/FileUploadOptions.md +33 -9
  99. package/docs/api/interfaces/FileUploadProps.md +36 -14
  100. package/docs/api/interfaces/FooterProps.md +1 -1
  101. package/docs/api/interfaces/FormFieldProps.md +1 -1
  102. package/docs/api/interfaces/FormProps.md +1 -1
  103. package/docs/api/interfaces/GrantEventAppRoleParams.md +1 -1
  104. package/docs/api/interfaces/InactivityWarningModalProps.md +1 -1
  105. package/docs/api/interfaces/InputProps.md +1 -1
  106. package/docs/api/interfaces/LabelProps.md +1 -1
  107. package/docs/api/interfaces/LoggerConfig.md +1 -1
  108. package/docs/api/interfaces/LoginFormProps.md +1 -1
  109. package/docs/api/interfaces/NavigationAccessRecord.md +1 -1
  110. package/docs/api/interfaces/NavigationContextType.md +1 -1
  111. package/docs/api/interfaces/NavigationGuardProps.md +1 -1
  112. package/docs/api/interfaces/NavigationItem.md +1 -1
  113. package/docs/api/interfaces/NavigationMenuProps.md +1 -1
  114. package/docs/api/interfaces/NavigationProviderProps.md +1 -1
  115. package/docs/api/interfaces/Organisation.md +1 -1
  116. package/docs/api/interfaces/OrganisationContextType.md +1 -1
  117. package/docs/api/interfaces/OrganisationMembership.md +1 -1
  118. package/docs/api/interfaces/OrganisationProviderProps.md +1 -1
  119. package/docs/api/interfaces/OrganisationSecurityError.md +1 -1
  120. package/docs/api/interfaces/PaceAppLayoutProps.md +1 -1
  121. package/docs/api/interfaces/PaceLoginPageProps.md +1 -1
  122. package/docs/api/interfaces/PageAccessRecord.md +1 -1
  123. package/docs/api/interfaces/PagePermissionContextType.md +1 -1
  124. package/docs/api/interfaces/PagePermissionGuardProps.md +11 -11
  125. package/docs/api/interfaces/PagePermissionProviderProps.md +1 -1
  126. package/docs/api/interfaces/PaletteData.md +1 -1
  127. package/docs/api/interfaces/ParsedAddress.md +120 -0
  128. package/docs/api/interfaces/PermissionEnforcerProps.md +1 -1
  129. package/docs/api/interfaces/ProgressProps.md +1 -1
  130. package/docs/api/interfaces/ProtectedRouteProps.md +6 -6
  131. package/docs/api/interfaces/PublicPageFooterProps.md +1 -1
  132. package/docs/api/interfaces/PublicPageHeaderProps.md +1 -1
  133. package/docs/api/interfaces/PublicPageLayoutProps.md +1 -1
  134. package/docs/api/interfaces/QuickFix.md +1 -1
  135. package/docs/api/interfaces/RBACAccessValidateParams.md +1 -1
  136. package/docs/api/interfaces/RBACAccessValidateResult.md +1 -1
  137. package/docs/api/interfaces/RBACAuditLogParams.md +1 -1
  138. package/docs/api/interfaces/RBACAuditLogResult.md +1 -1
  139. package/docs/api/interfaces/RBACConfig.md +27 -4
  140. package/docs/api/interfaces/RBACContext.md +1 -1
  141. package/docs/api/interfaces/RBACLogger.md +5 -5
  142. package/docs/api/interfaces/RBACPageAccessCheckParams.md +1 -1
  143. package/docs/api/interfaces/RBACPerformanceMetrics.md +138 -0
  144. package/docs/api/interfaces/RBACPermissionCheckParams.md +1 -1
  145. package/docs/api/interfaces/RBACPermissionCheckResult.md +1 -1
  146. package/docs/api/interfaces/RBACPermissionsGetParams.md +1 -1
  147. package/docs/api/interfaces/RBACPermissionsGetResult.md +1 -1
  148. package/docs/api/interfaces/RBACResult.md +1 -1
  149. package/docs/api/interfaces/RBACRoleGrantParams.md +1 -1
  150. package/docs/api/interfaces/RBACRoleGrantResult.md +1 -1
  151. package/docs/api/interfaces/RBACRoleRevokeParams.md +1 -1
  152. package/docs/api/interfaces/RBACRoleRevokeResult.md +1 -1
  153. package/docs/api/interfaces/RBACRoleValidateParams.md +1 -1
  154. package/docs/api/interfaces/RBACRoleValidateResult.md +1 -1
  155. package/docs/api/interfaces/RBACRolesListParams.md +1 -1
  156. package/docs/api/interfaces/RBACRolesListResult.md +1 -1
  157. package/docs/api/interfaces/RBACSessionTrackParams.md +1 -1
  158. package/docs/api/interfaces/RBACSessionTrackResult.md +1 -1
  159. package/docs/api/interfaces/ResourcePermissions.md +1 -1
  160. package/docs/api/interfaces/RevokeEventAppRoleParams.md +1 -1
  161. package/docs/api/interfaces/RoleBasedRouterContextType.md +1 -1
  162. package/docs/api/interfaces/RoleBasedRouterProps.md +1 -1
  163. package/docs/api/interfaces/RoleManagementResult.md +1 -1
  164. package/docs/api/interfaces/RouteAccessRecord.md +1 -1
  165. package/docs/api/interfaces/RouteConfig.md +1 -1
  166. package/docs/api/interfaces/RuntimeComplianceResult.md +1 -1
  167. package/docs/api/interfaces/SecureDataContextType.md +1 -1
  168. package/docs/api/interfaces/SecureDataProviderProps.md +1 -1
  169. package/docs/api/interfaces/SessionRestorationLoaderProps.md +1 -1
  170. package/docs/api/interfaces/SetupIssue.md +1 -1
  171. package/docs/api/interfaces/StorageConfig.md +1 -1
  172. package/docs/api/interfaces/StorageFileInfo.md +1 -1
  173. package/docs/api/interfaces/StorageFileMetadata.md +1 -1
  174. package/docs/api/interfaces/StorageListOptions.md +1 -1
  175. package/docs/api/interfaces/StorageListResult.md +1 -1
  176. package/docs/api/interfaces/StorageUploadOptions.md +1 -1
  177. package/docs/api/interfaces/StorageUploadResult.md +1 -1
  178. package/docs/api/interfaces/StorageUrlOptions.md +1 -1
  179. package/docs/api/interfaces/StyleImport.md +1 -1
  180. package/docs/api/interfaces/SwitchProps.md +1 -1
  181. package/docs/api/interfaces/TabsContentProps.md +1 -1
  182. package/docs/api/interfaces/TabsListProps.md +1 -1
  183. package/docs/api/interfaces/TabsProps.md +1 -1
  184. package/docs/api/interfaces/TabsTriggerProps.md +1 -1
  185. package/docs/api/interfaces/TextareaProps.md +1 -1
  186. package/docs/api/interfaces/ToastActionElement.md +1 -1
  187. package/docs/api/interfaces/ToastProps.md +1 -1
  188. package/docs/api/interfaces/UnifiedAuthContextType.md +1 -1
  189. package/docs/api/interfaces/UnifiedAuthProviderProps.md +1 -1
  190. package/docs/api/interfaces/UseFormDialogOptions.md +1 -1
  191. package/docs/api/interfaces/UseFormDialogReturn.md +1 -1
  192. package/docs/api/interfaces/UseInactivityTrackerOptions.md +1 -1
  193. package/docs/api/interfaces/UseInactivityTrackerReturn.md +1 -1
  194. package/docs/api/interfaces/UsePublicEventLogoOptions.md +2 -2
  195. package/docs/api/interfaces/UsePublicEventLogoReturn.md +1 -1
  196. package/docs/api/interfaces/UsePublicEventOptions.md +1 -1
  197. package/docs/api/interfaces/UsePublicEventReturn.md +1 -1
  198. package/docs/api/interfaces/UsePublicFileDisplayOptions.md +2 -2
  199. package/docs/api/interfaces/UsePublicFileDisplayReturn.md +1 -1
  200. package/docs/api/interfaces/UsePublicRouteParamsReturn.md +1 -1
  201. package/docs/api/interfaces/UseResolvedScopeOptions.md +2 -2
  202. package/docs/api/interfaces/UseResolvedScopeReturn.md +1 -1
  203. package/docs/api/interfaces/UseResourcePermissionsOptions.md +1 -1
  204. package/docs/api/interfaces/UserEventAccess.md +1 -1
  205. package/docs/api/interfaces/UserMenuProps.md +1 -1
  206. package/docs/api/interfaces/UserProfile.md +1 -1
  207. package/docs/api/modules.md +328 -69
  208. package/docs/api-reference/components.md +26 -12
  209. package/docs/best-practices/performance.md +11 -0
  210. package/docs/implementation-guides/file-reference-system.md +24 -2
  211. package/docs/implementation-guides/file-upload-storage.md +38 -1
  212. package/docs/rbac/README.md +2 -1
  213. package/docs/rbac/api-reference.md +11 -0
  214. package/docs/rbac/performance.md +320 -0
  215. package/docs/standards/01-architecture-standard.md +5 -0
  216. package/docs/standards/05-security-standard.md +12 -0
  217. package/package.json +1 -1
  218. package/scripts/check-pace-core-compliance.js +512 -0
  219. package/src/components/AddressField/AddressField.test.tsx +411 -0
  220. package/src/components/AddressField/AddressField.tsx +323 -0
  221. package/src/components/AddressField/README.md +336 -0
  222. package/src/components/AddressField/index.ts +10 -0
  223. package/src/components/AddressField/types.ts +65 -0
  224. package/src/components/FileDisplay/FileDisplay.test.tsx +454 -0
  225. package/src/components/FileDisplay/FileDisplay.tsx +28 -1
  226. package/src/components/FileUpload/FileUpload.test.tsx +2 -0
  227. package/src/components/FileUpload/FileUpload.tsx +7 -1
  228. package/src/components/Header/Header.tsx +2 -5
  229. package/src/components/ProtectedRoute/ProtectedRoute.tsx +134 -1
  230. package/src/components/index.ts +2 -0
  231. package/src/hooks/__tests__/useFileDisplay.unit.test.ts +30 -5
  232. package/src/hooks/__tests__/useOrganisationSecurity.unit.test.tsx +11 -10
  233. package/src/hooks/__tests__/usePublicFileDisplay.test.ts +31 -6
  234. package/src/hooks/index.ts +9 -0
  235. package/src/hooks/public/usePublicFileDisplay.ts +8 -10
  236. package/src/hooks/useAddressAutocomplete.test.ts +318 -0
  237. package/src/hooks/useAddressAutocomplete.ts +268 -0
  238. package/src/hooks/useFileDisplay.ts +3 -15
  239. package/src/hooks/useFileReference.test.ts +21 -3
  240. package/src/hooks/useFileReference.ts +3 -24
  241. package/src/hooks/useFileUrlCache.ts +246 -0
  242. package/src/hooks/useInactivityTracker.ts +31 -20
  243. package/src/hooks/useOrganisationSecurity.test.ts +10 -7
  244. package/src/hooks/useOrganisationSecurity.ts +3 -3
  245. package/src/hooks/usePreventTabReload.ts +106 -0
  246. package/src/hooks/useQueryCache.ts +315 -0
  247. package/src/hooks/useSecureDataAccess.ts +2 -2
  248. package/src/index.ts +2 -0
  249. package/src/providers/services/EventServiceProvider.tsx +4 -1
  250. package/src/rbac/__tests__/rbac-role-isolation.test.ts +456 -0
  251. package/src/rbac/api.test.ts +21 -6
  252. package/src/rbac/api.ts +32 -11
  253. package/src/rbac/audit-batched.ts +223 -0
  254. package/src/rbac/audit-enhanced.ts +2 -2
  255. package/src/rbac/audit.test.ts +6 -5
  256. package/src/rbac/audit.ts +34 -6
  257. package/src/rbac/cache-invalidation.ts +63 -12
  258. package/src/rbac/cache.test.ts +2 -2
  259. package/src/rbac/cache.ts +61 -14
  260. package/src/rbac/components/PagePermissionGuard.tsx +19 -10
  261. package/src/rbac/components/__tests__/PagePermissionGuard.performance.test.tsx +248 -0
  262. package/src/rbac/config.ts +9 -0
  263. package/src/rbac/engine.ts +2 -21
  264. package/src/rbac/hooks/usePermissions.ts +21 -5
  265. package/src/rbac/index.ts +19 -0
  266. package/src/rbac/performance.ts +210 -0
  267. package/src/rbac/request-deduplication.ts +87 -0
  268. package/src/rbac/utils/deep-equal.ts +93 -0
  269. package/src/styles/core.css +5 -5
  270. package/src/types/database.generated.ts +63 -9
  271. package/src/types/file-reference.ts +3 -1
  272. package/src/utils/file-reference/__tests__/file-reference.test.ts +89 -8
  273. package/src/utils/file-reference/index.ts +56 -17
  274. package/src/utils/google-places/googlePlacesUtils.test.ts +403 -0
  275. package/src/utils/google-places/googlePlacesUtils.ts +475 -0
  276. package/src/utils/google-places/index.ts +26 -0
  277. package/src/utils/google-places/loadGoogleMapsScript.ts +207 -0
  278. package/src/utils/google-places/types.ts +94 -0
  279. package/src/utils/index.ts +23 -0
  280. package/src/utils/request-deduplication.ts +165 -0
  281. package/src/utils/security/secureDataAccess.ts +1 -1
  282. package/src/utils/storage/helpers.ts +211 -4
  283. package/dist/chunk-445GEP27.js.map +0 -1
  284. package/dist/chunk-AISXLWGZ.js.map +0 -1
  285. package/dist/chunk-FMUCXFII.js +0 -76
  286. package/dist/chunk-FMUCXFII.js.map +0 -1
  287. package/dist/chunk-FSFQFJCU.js.map +0 -1
  288. package/dist/chunk-FXFJRTKI.js.map +0 -1
  289. package/dist/chunk-HC67NW5K.js.map +0 -1
  290. package/dist/chunk-IXSNYUCT.js.map +0 -1
  291. package/dist/chunk-MX3EIJGQ.js.map +0 -1
  292. package/dist/chunk-OKI34GZD.js.map +0 -1
  293. package/dist/chunk-STTZQK2I.js.map +0 -1
  294. package/dist/chunk-U6WNSFX5.js.map +0 -1
  295. /package/dist/{DataTable-IX2NBUTP.js.map → DataTable-K3RJRSOX.js.map} +0 -0
  296. /package/dist/{UnifiedAuthProvider-A4BCQRJY.js.map → UnifiedAuthProvider-B76OWOAT.js.map} +0 -0
  297. /package/dist/{api-BMFCXVQX.js.map → api-YP7XD5L6.js.map} +0 -0
  298. /package/dist/{audit-WRS3KJKI.js.map → audit-B5P6FFIR.js.map} +0 -0
  299. /package/dist/{chunk-HGPQUCBC.js.map → chunk-FMTK4XNN.js.map} +0 -0
  300. /package/dist/{chunk-XAUHJD3L.js.map → chunk-K2JGDXGU.js.map} +0 -0
@@ -0,0 +1,512 @@
1
+ #!/usr/bin/env node
2
+
3
+ /**
4
+ * Static Analysis Script for pace-core Compliance
5
+ * @package @jmruthers/pace-core
6
+ * @module Scripts/check-pace-core-compliance
7
+ *
8
+ * Scans a consuming app's codebase to check compliance with pace-core usage.
9
+ * Generates a report of violations and suggestions.
10
+ */
11
+
12
+ const fs = require('fs');
13
+ const path = require('path');
14
+
15
+ // ANSI color codes for terminal output
16
+ const colors = {
17
+ reset: '\x1b[0m',
18
+ red: '\x1b[31m',
19
+ green: '\x1b[32m',
20
+ yellow: '\x1b[33m',
21
+ blue: '\x1b[34m',
22
+ cyan: '\x1b[36m',
23
+ bold: '\x1b[1m'
24
+ };
25
+
26
+ // Load manifest
27
+ function loadManifest() {
28
+ const manifestPath = path.join(__dirname, '../core-usage-manifest.json');
29
+ if (!fs.existsSync(manifestPath)) {
30
+ console.error(`${colors.red}Error: core-usage-manifest.json not found at ${manifestPath}${colors.reset}`);
31
+ process.exit(1);
32
+ }
33
+ return JSON.parse(fs.readFileSync(manifestPath, 'utf8'));
34
+ }
35
+
36
+ // Find project root (look for package.json, going up from current dir or script location)
37
+ function findProjectRoot(startDir = process.cwd()) {
38
+ let current = path.resolve(startDir);
39
+ while (current !== path.dirname(current)) {
40
+ if (fs.existsSync(path.join(current, 'package.json'))) {
41
+ return current;
42
+ }
43
+ current = path.dirname(current);
44
+ }
45
+ return startDir;
46
+ }
47
+
48
+ // Scan file for violations
49
+ function scanFile(filePath, manifest) {
50
+ const violations = {
51
+ restrictedImports: [],
52
+ duplicateComponents: [],
53
+ duplicateHooks: [],
54
+ duplicateUtils: [],
55
+ suggestions: [],
56
+ customAuthCode: [],
57
+ duplicateConfig: [],
58
+ unprotectedPages: [],
59
+ directSupabaseAuth: []
60
+ };
61
+
62
+ const content = fs.readFileSync(filePath, 'utf8');
63
+ const relativePath = path.relative(process.cwd(), filePath);
64
+
65
+ // Check for restricted imports
66
+ manifest.restrictedImports.forEach(({ module, reason }) => {
67
+ const importPattern = new RegExp(`from\\s+['"]${module.replace(/[.*+?^${}()|[\]\\]/g, '\\$&')}['"]`, 'g');
68
+ if (importPattern.test(content)) {
69
+ violations.restrictedImports.push({
70
+ module,
71
+ reason,
72
+ file: relativePath,
73
+ line: getLineNumber(content, content.match(importPattern)[0])
74
+ });
75
+ }
76
+
77
+ // Also check for @radix-ui/* pattern
78
+ if (module.startsWith('@radix-ui/')) {
79
+ const radixPattern = /from\s+['"]@radix-ui\/[^'"]+['"]/g;
80
+ const matches = content.match(radixPattern);
81
+ if (matches) {
82
+ matches.forEach(match => {
83
+ const matchedModule = match.match(/['"]([^'"]+)['"]/)[1];
84
+ if (!manifest.restrictedImports.find(ri => ri.module === matchedModule)) {
85
+ violations.restrictedImports.push({
86
+ module: matchedModule,
87
+ reason: 'Use pace-core component instead of direct Radix UI import',
88
+ file: relativePath,
89
+ line: getLineNumber(content, match)
90
+ });
91
+ }
92
+ });
93
+ }
94
+ }
95
+ });
96
+
97
+ // Check for duplicate component names
98
+ const filename = path.basename(filePath);
99
+ const componentName = filename.replace(/\.(tsx?|jsx?)$/, '').replace(/\.(test|spec)$/, '');
100
+
101
+ if (manifest.components.includes(componentName)) {
102
+ // Check if this file exports a component
103
+ if (content.match(/export\s+(default\s+)?(function|const|class)\s+(\w+)?/)) {
104
+ violations.duplicateComponents.push({
105
+ component: componentName,
106
+ file: relativePath
107
+ });
108
+ }
109
+ }
110
+
111
+ // Check for duplicate hook names
112
+ if (filename.startsWith('use') && filename.endsWith('.ts') || filename.endsWith('.tsx')) {
113
+ const hookName = filename.replace(/\.(tsx?|jsx?)$/, '').replace(/\.(test|spec)$/, '');
114
+ if (manifest.hooks.includes(hookName)) {
115
+ if (content.match(/export\s+(default\s+)?(function|const)\s+(\w+)?/)) {
116
+ violations.duplicateHooks.push({
117
+ hook: hookName,
118
+ file: relativePath
119
+ });
120
+ }
121
+ }
122
+ }
123
+
124
+ // Check for duplicate util names
125
+ const utilName = filename.replace(/\.(ts|js)$/, '').replace(/\.(test|spec)$/, '');
126
+ if (manifest.utils.includes(utilName)) {
127
+ if (content.match(/export\s+(default\s+)?(function|const)\s+(\w+)?/)) {
128
+ violations.duplicateUtils.push({
129
+ util: utilName,
130
+ file: relativePath
131
+ });
132
+ }
133
+ }
134
+
135
+ // Check for native HTML elements that should use pace-core components
136
+ const nativeElementPatterns = {
137
+ '<button': { suggestion: 'Use Button from @jmruthers/pace-core' },
138
+ '<input': { suggestion: 'Use Input from @jmruthers/pace-core' },
139
+ '<textarea': { suggestion: 'Use Textarea from @jmruthers/pace-core' },
140
+ '<label': { suggestion: 'Use Label from @jmruthers/pace-core' }
141
+ };
142
+
143
+ Object.entries(nativeElementPatterns).forEach(([pattern, { suggestion }]) => {
144
+ if (content.includes(pattern) && !content.includes('from \'@jmruthers/pace-core\'')) {
145
+ violations.suggestions.push({
146
+ type: 'native-element',
147
+ suggestion,
148
+ file: relativePath,
149
+ pattern
150
+ });
151
+ }
152
+ });
153
+
154
+ // ============================================
155
+ // RBAC/Auth Compliance Checks
156
+ // ============================================
157
+
158
+ // Check for custom auth/rbac/permission code that doesn't import from pace-core
159
+ const authRbacPatterns = [
160
+ // Custom auth hooks
161
+ { pattern: /export\s+(default\s+)?(function|const)\s+useAuth\s*[=\(]/g, name: 'useAuth', type: 'hook' },
162
+ { pattern: /export\s+(default\s+)?(function|const)\s+useLogin\s*[=\(]/g, name: 'useLogin', type: 'hook' },
163
+ { pattern: /export\s+(default\s+)?(function|const)\s+useLogout\s*[=\(]/g, name: 'useLogout', type: 'hook' },
164
+ { pattern: /export\s+(default\s+)?(function|const)\s+useSession\s*[=\(]/g, name: 'useSession', type: 'hook' },
165
+ { pattern: /export\s+(default\s+)?(function|const)\s+useUser\s*[=\(]/g, name: 'useUser', type: 'hook' },
166
+ { pattern: /export\s+(default\s+)?(function|const)\s+useAuthentication\s*[=\(]/g, name: 'useAuthentication', type: 'hook' },
167
+ // Custom RBAC hooks
168
+ { pattern: /export\s+(default\s+)?(function|const)\s+usePermissions\s*[=\(]/g, name: 'usePermissions', type: 'hook' },
169
+ { pattern: /export\s+(default\s+)?(function|const)\s+useCan\s*[=\(]/g, name: 'useCan', type: 'hook' },
170
+ { pattern: /export\s+(default\s+)?(function|const)\s+useAccessLevel\s*[=\(]/g, name: 'useAccessLevel', type: 'hook' },
171
+ { pattern: /export\s+(default\s+)?(function|const)\s+useRole\s*[=\(]/g, name: 'useRole', type: 'hook' },
172
+ // Custom RBAC components
173
+ { pattern: /export\s+(default\s+)?(function|const)\s+PermissionGuard\s*[=\(]/g, name: 'PermissionGuard', type: 'component' },
174
+ { pattern: /export\s+(default\s+)?(function|const)\s+AuthGuard\s*[=\(]/g, name: 'AuthGuard', type: 'component' },
175
+ { pattern: /export\s+(default\s+)?(function|const)\s+RoleGuard\s*[=\(]/g, name: 'RoleGuard', type: 'component' },
176
+ { pattern: /export\s+(default\s+)?(function|const)\s+AccessGuard\s*[=\(]/g, name: 'AccessGuard', type: 'component' },
177
+ // Custom permission utilities
178
+ { pattern: /export\s+(default\s+)?(function|const)\s+checkPermission\s*[=\(]/g, name: 'checkPermission', type: 'util' },
179
+ { pattern: /export\s+(default\s+)?(function|const)\s+hasPermission\s*[=\(]/g, name: 'hasPermission', type: 'util' },
180
+ { pattern: /export\s+(default\s+)?(function|const)\s+hasAccess\s*[=\(]/g, name: 'hasAccess', type: 'util' },
181
+ { pattern: /export\s+(default\s+)?(function|const)\s+canAccess\s*[=\(]/g, name: 'canAccess', type: 'util' },
182
+ { pattern: /export\s+(default\s+)?(function|const)\s+isPermitted\s*[=\(]/g, name: 'isPermitted', type: 'util' }
183
+ ];
184
+
185
+ // Check if file imports from pace-core for auth/rbac
186
+ const hasPaceCoreImport = /from\s+['"]@jmruthers\/pace-core/.test(content) ||
187
+ /from\s+['"]@jmruthers\/pace-core\/rbac/.test(content) ||
188
+ /from\s+['"]@jmruthers\/pace-core\/providers/.test(content);
189
+
190
+ authRbacPatterns.forEach(({ pattern, name, type }) => {
191
+ if (pattern.test(content) && !hasPaceCoreImport) {
192
+ violations.customAuthCode.push({
193
+ name,
194
+ type,
195
+ file: relativePath,
196
+ line: getLineNumber(content, content.match(pattern)[0]),
197
+ reason: `Custom ${type} '${name}' detected. Use pace-core's ${name} instead.`
198
+ });
199
+ }
200
+ });
201
+
202
+ // Check for duplicate Supabase client configurations
203
+ const supabaseCreateClientPattern = /createClient\s*\(/g;
204
+ const supabaseCreateClientMatches = content.match(supabaseCreateClientPattern);
205
+ if (supabaseCreateClientMatches && supabaseCreateClientMatches.length > 1) {
206
+ violations.duplicateConfig.push({
207
+ type: 'supabase-client',
208
+ file: relativePath,
209
+ count: supabaseCreateClientMatches.length,
210
+ reason: `Multiple Supabase client instantiations found (${supabaseCreateClientMatches.length}). Consolidate to a single client configuration.`
211
+ });
212
+ }
213
+
214
+ // Check for Supabase URL/key configuration in multiple places
215
+ const supabaseUrlPattern = /(SUPABASE_URL|VITE_SUPABASE_URL|NEXT_PUBLIC_SUPABASE_URL|REACT_APP_SUPABASE_URL)/g;
216
+ const supabaseKeyPattern = /(SUPABASE_ANON_KEY|VITE_SUPABASE_ANON_KEY|NEXT_PUBLIC_SUPABASE_ANON_KEY|REACT_APP_SUPABASE_ANON_KEY)/g;
217
+ const urlMatches = content.match(supabaseUrlPattern);
218
+ const keyMatches = content.match(supabaseKeyPattern);
219
+
220
+ if ((urlMatches && urlMatches.length > 2) || (keyMatches && keyMatches.length > 2)) {
221
+ violations.duplicateConfig.push({
222
+ type: 'supabase-env',
223
+ file: relativePath,
224
+ reason: 'Supabase environment variables referenced multiple times. Consider centralizing configuration.'
225
+ });
226
+ }
227
+
228
+ // Check for unprotected pages/routes
229
+ // Look for route definitions without PagePermissionGuard
230
+ const routePatterns = [
231
+ /<Route\s+path=["'][^"']+["']/g,
232
+ /<Route\s+element\s*=/g,
233
+ /createBrowserRouter\s*\(/g,
234
+ /createRoutesFromElements/g
235
+ ];
236
+
237
+ const isRouteFile = routePatterns.some(pattern => pattern.test(content));
238
+ const hasPagePermissionGuard = /PagePermissionGuard/.test(content) ||
239
+ /from\s+['"]@jmruthers\/pace-core\/rbac['"]/.test(content);
240
+
241
+ if (isRouteFile && !hasPagePermissionGuard && !relativePath.includes('test') && !relativePath.includes('spec')) {
242
+ violations.unprotectedPages.push({
243
+ file: relativePath,
244
+ reason: 'Route file found without PagePermissionGuard. All routes should be protected with PagePermissionGuard from pace-core.'
245
+ });
246
+ }
247
+
248
+ // Check for direct Supabase auth usage (should use UnifiedAuthProvider)
249
+ const directSupabaseAuthPatterns = [
250
+ /\.auth\.signIn/g,
251
+ /\.auth\.signUp/g,
252
+ /\.auth\.signOut/g,
253
+ /\.auth\.getSession/g,
254
+ /\.auth\.getUser/g,
255
+ /supabase\.auth\./g
256
+ ];
257
+
258
+ const hasUnifiedAuthImport = /UnifiedAuthProvider/.test(content) ||
259
+ /useUnifiedAuth/.test(content) ||
260
+ /from\s+['"]@jmruthers\/pace-core\/providers/.test(content);
261
+
262
+ directSupabaseAuthPatterns.forEach(pattern => {
263
+ if (pattern.test(content) && !hasUnifiedAuthImport && !hasPaceCoreImport) {
264
+ violations.directSupabaseAuth.push({
265
+ file: relativePath,
266
+ line: getLineNumber(content, content.match(pattern)[0]),
267
+ reason: 'Direct Supabase auth usage detected. Use UnifiedAuthProvider and useUnifiedAuth from pace-core instead.'
268
+ });
269
+ }
270
+ });
271
+
272
+ return violations;
273
+ }
274
+
275
+ // Get line number for a match
276
+ function getLineNumber(content, match) {
277
+ const lines = content.substring(0, content.indexOf(match)).split('\n');
278
+ return lines.length;
279
+ }
280
+
281
+ // Generate report
282
+ function generateReport(allViolations, manifest) {
283
+ const totalRestricted = allViolations.restrictedImports.length;
284
+ const totalDuplicates =
285
+ allViolations.duplicateComponents.length +
286
+ allViolations.duplicateHooks.length +
287
+ allViolations.duplicateUtils.length;
288
+ const totalSuggestions = allViolations.suggestions.length;
289
+ const totalRbacAuth =
290
+ allViolations.customAuthCode.length +
291
+ allViolations.duplicateConfig.length +
292
+ allViolations.unprotectedPages.length +
293
+ allViolations.directSupabaseAuth.length;
294
+ const totalIssues = totalRestricted + totalDuplicates + totalSuggestions + totalRbacAuth;
295
+
296
+ console.log(`\n${colors.bold}${colors.cyan}═══════════════════════════════════════════════════════════${colors.reset}`);
297
+ console.log(`${colors.bold}${colors.cyan} pace-core Compliance Report${colors.reset}`);
298
+ console.log(`${colors.bold}${colors.cyan}═══════════════════════════════════════════════════════════${colors.reset}\n`);
299
+
300
+ // Restricted Imports
301
+ if (totalRestricted > 0) {
302
+ console.log(`${colors.red}${colors.bold}❌ Restricted Imports Found: ${totalRestricted}${colors.reset}\n`);
303
+ allViolations.restrictedImports.forEach(({ module, reason, file, line }) => {
304
+ console.log(` ${colors.red}•${colors.reset} ${colors.yellow}${file}:${line}${colors.reset}`);
305
+ console.log(` Import: ${colors.cyan}${module}${colors.reset}`);
306
+ console.log(` ${colors.yellow}Reason:${colors.reset} ${reason}\n`);
307
+ });
308
+ } else {
309
+ console.log(`${colors.green}✅ No restricted imports found${colors.reset}\n`);
310
+ }
311
+
312
+ // Duplicate Components
313
+ if (allViolations.duplicateComponents.length > 0) {
314
+ console.log(`${colors.red}${colors.bold}❌ Duplicate Components Found: ${allViolations.duplicateComponents.length}${colors.reset}\n`);
315
+ allViolations.duplicateComponents.forEach(({ component, file }) => {
316
+ console.log(` ${colors.red}•${colors.reset} ${colors.yellow}${file}${colors.reset}`);
317
+ console.log(` Component '${colors.cyan}${component}${colors.reset}' conflicts with pace-core component`);
318
+ console.log(` ${colors.yellow}Suggestion:${colors.reset} Use '${component}' from '@jmruthers/pace-core' instead\n`);
319
+ });
320
+ }
321
+
322
+ // Duplicate Hooks
323
+ if (allViolations.duplicateHooks.length > 0) {
324
+ console.log(`${colors.red}${colors.bold}❌ Duplicate Hooks Found: ${allViolations.duplicateHooks.length}${colors.reset}\n`);
325
+ allViolations.duplicateHooks.forEach(({ hook, file }) => {
326
+ console.log(` ${colors.red}•${colors.reset} ${colors.yellow}${file}${colors.reset}`);
327
+ console.log(` Hook '${colors.cyan}${hook}${colors.reset}' conflicts with pace-core hook`);
328
+ console.log(` ${colors.yellow}Suggestion:${colors.reset} Use '${hook}' from '@jmruthers/pace-core' instead\n`);
329
+ });
330
+ }
331
+
332
+ // Duplicate Utils
333
+ if (allViolations.duplicateUtils.length > 0) {
334
+ console.log(`${colors.red}${colors.bold}❌ Duplicate Utils Found: ${allViolations.duplicateUtils.length}${colors.reset}\n`);
335
+ allViolations.duplicateUtils.forEach(({ util, file }) => {
336
+ console.log(` ${colors.red}•${colors.reset} ${colors.yellow}${file}${colors.reset}`);
337
+ console.log(` Util '${colors.cyan}${util}${colors.reset}' conflicts with pace-core util`);
338
+ console.log(` ${colors.yellow}Suggestion:${colors.reset} Use '${util}' from '@jmruthers/pace-core' instead\n`);
339
+ });
340
+ }
341
+
342
+ // Suggestions
343
+ if (totalSuggestions > 0) {
344
+ console.log(`${colors.yellow}${colors.bold}💡 Suggestions: ${totalSuggestions}${colors.reset}\n`);
345
+ const grouped = {};
346
+ allViolations.suggestions.forEach(s => {
347
+ if (!grouped[s.file]) grouped[s.file] = [];
348
+ grouped[s.file].push(s);
349
+ });
350
+ Object.entries(grouped).forEach(([file, suggestions]) => {
351
+ console.log(` ${colors.yellow}•${colors.reset} ${colors.yellow}${file}${colors.reset}`);
352
+ suggestions.forEach(s => {
353
+ console.log(` ${s.suggestion}\n`);
354
+ });
355
+ });
356
+ }
357
+
358
+ // RBAC/Auth Compliance Section
359
+ if (totalRbacAuth > 0) {
360
+ console.log(`\n${colors.bold}${colors.cyan}═══════════════════════════════════════════════════════════${colors.reset}`);
361
+ console.log(`${colors.bold}${colors.cyan} RBAC/Auth Compliance${colors.reset}`);
362
+ console.log(`${colors.bold}${colors.cyan}═══════════════════════════════════════════════════════════${colors.reset}\n`);
363
+
364
+ // Custom Auth/RBAC Code
365
+ if (allViolations.customAuthCode.length > 0) {
366
+ console.log(`${colors.red}${colors.bold}❌ Custom Auth/RBAC Code Found: ${allViolations.customAuthCode.length}${colors.reset}\n`);
367
+ allViolations.customAuthCode.forEach(({ name, type, file, line, reason }) => {
368
+ console.log(` ${colors.red}•${colors.reset} ${colors.yellow}${file}:${line}${colors.reset}`);
369
+ console.log(` ${type}: ${colors.cyan}${name}${colors.reset}`);
370
+ console.log(` ${colors.yellow}Reason:${colors.reset} ${reason}\n`);
371
+ });
372
+ }
373
+
374
+ // Duplicate Configurations
375
+ if (allViolations.duplicateConfig.length > 0) {
376
+ console.log(`${colors.red}${colors.bold}❌ Duplicate Configurations Found: ${allViolations.duplicateConfig.length}${colors.reset}\n`);
377
+ allViolations.duplicateConfig.forEach(({ type, file, count, reason }) => {
378
+ console.log(` ${colors.red}•${colors.reset} ${colors.yellow}${file}${colors.reset}`);
379
+ console.log(` Type: ${colors.cyan}${type}${colors.reset}${count ? ` (${count} instances)` : ''}`);
380
+ console.log(` ${colors.yellow}Reason:${colors.reset} ${reason}\n`);
381
+ });
382
+ }
383
+
384
+ // Unprotected Pages
385
+ if (allViolations.unprotectedPages.length > 0) {
386
+ console.log(`${colors.red}${colors.bold}❌ Unprotected Pages Found: ${allViolations.unprotectedPages.length}${colors.reset}\n`);
387
+ allViolations.unprotectedPages.forEach(({ file, reason }) => {
388
+ console.log(` ${colors.red}•${colors.reset} ${colors.yellow}${file}${colors.reset}`);
389
+ console.log(` ${colors.yellow}Reason:${colors.reset} ${reason}\n`);
390
+ });
391
+ }
392
+
393
+ // Direct Supabase Auth Usage
394
+ if (allViolations.directSupabaseAuth.length > 0) {
395
+ console.log(`${colors.red}${colors.bold}❌ Direct Supabase Auth Usage Found: ${allViolations.directSupabaseAuth.length}${colors.reset}\n`);
396
+ allViolations.directSupabaseAuth.forEach(({ file, line, reason }) => {
397
+ console.log(` ${colors.red}•${colors.reset} ${colors.yellow}${file}:${line}${colors.reset}`);
398
+ console.log(` ${colors.yellow}Reason:${colors.reset} ${reason}\n`);
399
+ });
400
+ }
401
+ } else {
402
+ console.log(`\n${colors.green}✅ RBAC/Auth compliance: All checks passed${colors.reset}\n`);
403
+ }
404
+
405
+ // Summary
406
+ console.log(`${colors.bold}${colors.cyan}═══════════════════════════════════════════════════════════${colors.reset}`);
407
+ console.log(`${colors.bold}Summary:${colors.reset}`);
408
+ console.log(` Total Issues: ${totalIssues > 0 ? colors.red : colors.green}${totalIssues}${colors.reset}`);
409
+ console.log(` - Restricted Imports: ${totalRestricted > 0 ? colors.red : colors.green}${totalRestricted}${colors.reset}`);
410
+ console.log(` - Duplicate Components/Hooks/Utils: ${totalDuplicates > 0 ? colors.red : colors.green}${totalDuplicates}${colors.reset}`);
411
+ console.log(` - Suggestions: ${colors.yellow}${totalSuggestions}${colors.reset}`);
412
+ console.log(` - RBAC/Auth Issues: ${totalRbacAuth > 0 ? colors.red : colors.green}${totalRbacAuth}${colors.reset}`);
413
+
414
+ if (totalIssues === 0) {
415
+ console.log(`\n${colors.green}${colors.bold}✅ Excellent! Your codebase is fully compliant with pace-core standards.${colors.reset}\n`);
416
+ return 0;
417
+ } else {
418
+ console.log(`\n${colors.yellow}${colors.bold}⚠️ Please review the issues above and migrate to pace-core components/hooks/utils.${colors.reset}\n`);
419
+ return 1;
420
+ }
421
+ }
422
+
423
+ // Recursively find source files
424
+ function findSourceFiles(dir, fileList = []) {
425
+ const ignoreDirs = ['node_modules', 'dist', 'build', '.next', 'coverage', '__tests__', '__mocks__'];
426
+ const ignoreFiles = /\.(test|spec)\.(ts|tsx|js|jsx)$/;
427
+ const sourceExtensions = /\.(ts|tsx|js|jsx)$/;
428
+
429
+ try {
430
+ const items = fs.readdirSync(dir);
431
+
432
+ items.forEach(item => {
433
+ const fullPath = path.join(dir, item);
434
+ const stat = fs.statSync(fullPath);
435
+
436
+ if (stat.isDirectory()) {
437
+ if (!ignoreDirs.includes(item) && !item.startsWith('.')) {
438
+ findSourceFiles(fullPath, fileList);
439
+ }
440
+ } else if (stat.isFile()) {
441
+ if (sourceExtensions.test(item) && !ignoreFiles.test(item)) {
442
+ fileList.push(fullPath);
443
+ }
444
+ }
445
+ });
446
+ } catch (error) {
447
+ // Skip directories we can't read
448
+ }
449
+
450
+ return fileList;
451
+ }
452
+
453
+ // Main function
454
+ function main() {
455
+ const manifest = loadManifest();
456
+ const projectRoot = findProjectRoot();
457
+
458
+ console.log(`${colors.cyan}Scanning project at: ${projectRoot}${colors.reset}`);
459
+
460
+ // Find all TypeScript/JavaScript files (excluding node_modules, dist, etc.)
461
+ const files = findSourceFiles(projectRoot);
462
+
463
+ console.log(`Found ${files.length} files to scan...\n`);
464
+
465
+ // Scan all files
466
+ const allViolations = {
467
+ restrictedImports: [],
468
+ duplicateComponents: [],
469
+ duplicateHooks: [],
470
+ duplicateUtils: [],
471
+ suggestions: [],
472
+ customAuthCode: [],
473
+ duplicateConfig: [],
474
+ unprotectedPages: [],
475
+ directSupabaseAuth: []
476
+ };
477
+
478
+ files.forEach(file => {
479
+ try {
480
+ const violations = scanFile(file, manifest);
481
+ allViolations.restrictedImports.push(...violations.restrictedImports);
482
+ allViolations.duplicateComponents.push(...violations.duplicateComponents);
483
+ allViolations.duplicateHooks.push(...violations.duplicateHooks);
484
+ allViolations.duplicateUtils.push(...violations.duplicateUtils);
485
+ allViolations.suggestions.push(...violations.suggestions);
486
+ allViolations.customAuthCode.push(...violations.customAuthCode);
487
+ allViolations.duplicateConfig.push(...violations.duplicateConfig);
488
+ allViolations.unprotectedPages.push(...violations.unprotectedPages);
489
+ allViolations.directSupabaseAuth.push(...violations.directSupabaseAuth);
490
+ } catch (error) {
491
+ console.error(`${colors.red}Error scanning ${file}: ${error.message}${colors.reset}`);
492
+ }
493
+ });
494
+
495
+ // Generate and display report
496
+ const exitCode = generateReport(allViolations, manifest);
497
+ process.exit(exitCode);
498
+ }
499
+
500
+ // Run if called directly
501
+ if (require.main === module) {
502
+ try {
503
+ main();
504
+ } catch (error) {
505
+ console.error(`${colors.red}Error: ${error.message}${colors.reset}`);
506
+ console.error(error.stack);
507
+ process.exit(1);
508
+ }
509
+ }
510
+
511
+ module.exports = { main, scanFile, generateReport };
512
+