@jmruthers/pace-core 0.6.6 → 0.6.7

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (246) hide show
  1. package/{scripts/audit/audit-dependencies.cjs → audit-tool/00-dependencies.cjs} +12 -13
  2. package/audit-tool/audits/01-pace-core-compliance.cjs +556 -0
  3. package/audit-tool/audits/02-project-structure.cjs +255 -0
  4. package/audit-tool/audits/03-architecture.cjs +196 -0
  5. package/audit-tool/audits/04-code-quality.cjs +149 -0
  6. package/audit-tool/audits/05-styling.cjs +224 -0
  7. package/audit-tool/audits/06-security-rbac.cjs +544 -0
  8. package/audit-tool/audits/07-api-tech-stack.cjs +301 -0
  9. package/audit-tool/audits/08-testing-documentation.cjs +202 -0
  10. package/audit-tool/audits/09-operations.cjs +208 -0
  11. package/audit-tool/index.cjs +291 -0
  12. package/audit-tool/utils/code-utils.cjs +218 -0
  13. package/audit-tool/utils/file-utils.cjs +230 -0
  14. package/audit-tool/utils/report-utils.cjs +241 -0
  15. package/cursor-rules/00-standards-overview.mdc +156 -0
  16. package/cursor-rules/{00-pace-core-compliance.mdc → 01-pace-core-compliance.mdc} +187 -34
  17. package/cursor-rules/02-project-structure.mdc +37 -5
  18. package/cursor-rules/{03-solid-principles.mdc → 03-architecture.mdc} +125 -11
  19. package/cursor-rules/04-code-quality.mdc +419 -0
  20. package/cursor-rules/{08-markup-quality.mdc → 05-styling.mdc} +55 -10
  21. package/cursor-rules/{09-rbac-compliance.mdc → 06-security-rbac.mdc} +62 -6
  22. package/cursor-rules/07-api-tech-stack.mdc +377 -0
  23. package/cursor-rules/08-testing-documentation.mdc +324 -0
  24. package/cursor-rules/09-operations.mdc +365 -0
  25. package/dist/DataTable-7PMH7XN7.js +15 -0
  26. package/dist/{DataTable-2N_tqbfq.d.ts → DataTable-DRUIgtUH.d.ts} +1 -1
  27. package/dist/{PublicPageProvider-BBH6Vqg7.d.ts → PublicPageProvider-DlsCaR5v.d.ts} +26 -16
  28. package/dist/{chunk-FENMYN2U.js → chunk-5X4QLXRG.js} +1 -3
  29. package/dist/{chunk-4T7OBVTU.js → chunk-6F3IILHI.js} +1 -1
  30. package/dist/{chunk-SD6WQY43.js → chunk-7ILTDCL2.js} +9 -1
  31. package/dist/{chunk-3QC3KRHK.js → chunk-A3W6LW53.js} +16 -1
  32. package/dist/{chunk-7TYHROIV.js → chunk-BM4CQ5P3.js} +50 -8
  33. package/dist/{chunk-2HGJFNAH.js → chunk-FEJLJNWA.js} +1 -15
  34. package/dist/{chunk-OHIK3MIO.js → chunk-GHYHJTYV.js} +2 -2
  35. package/dist/{chunk-UIYSCEV7.js → chunk-IUBRCBSY.js} +1 -1
  36. package/dist/{chunk-LAZMKTTF.js → chunk-JGWDVX64.js} +281 -347
  37. package/dist/{chunk-MAGBIDNS.js → chunk-L4XMVJKY.js} +2 -2
  38. package/dist/{chunk-A55DK444.js → chunk-OJ4SKRSV.js} +1 -7
  39. package/dist/{chunk-ZS5VO5JB.js → chunk-Q7Q7V5NV.js} +406 -451
  40. package/dist/{chunk-3O3WHILE.js → chunk-VBCS3DUA.js} +236 -60
  41. package/dist/{chunk-BVP2BCJF.js → chunk-ZKAWKYT4.js} +8 -8
  42. package/dist/components.d.ts +5 -4
  43. package/dist/components.js +27 -32
  44. package/dist/eslint-rules/index.cjs +22 -9
  45. package/{src/eslint-rules/rules/compliance.cjs → dist/eslint-rules/rules/01-pace-core-compliance.cjs} +184 -23
  46. package/dist/eslint-rules/rules/04-code-quality.cjs +290 -0
  47. package/dist/eslint-rules/rules/05-styling.cjs +61 -0
  48. package/dist/eslint-rules/rules/{rbac.cjs → 06-security-rbac.cjs} +26 -10
  49. package/dist/eslint-rules/rules/07-api-tech-stack.cjs +263 -0
  50. package/dist/eslint-rules/rules/08-testing.cjs +94 -0
  51. package/dist/hooks.d.ts +5 -5
  52. package/dist/hooks.js +6 -6
  53. package/dist/index.d.ts +6 -6
  54. package/dist/index.js +18 -17
  55. package/dist/rbac/index.js +6 -6
  56. package/dist/theming/runtime.d.ts +14 -1
  57. package/dist/theming/runtime.js +1 -1
  58. package/dist/{types-B-K_5VnO.d.ts → types-DXstZpNI.d.ts} +0 -17
  59. package/dist/{usePublicRouteParams-COZ28Mvq.d.ts → usePublicRouteParams-MamNgwqe.d.ts} +19 -19
  60. package/dist/utils.d.ts +2 -2
  61. package/dist/utils.js +8 -8
  62. package/docs/README.md +1 -1
  63. package/docs/api/modules.md +47 -31
  64. package/docs/api-reference/components.md +18 -20
  65. package/docs/api-reference/hooks.md +80 -80
  66. package/docs/api-reference/types.md +1 -1
  67. package/docs/api-reference/utilities.md +1 -1
  68. package/docs/architecture/README.md +1 -1
  69. package/docs/core-concepts/events.md +3 -3
  70. package/docs/core-concepts/organisations.md +6 -6
  71. package/docs/core-concepts/permissions.md +6 -6
  72. package/docs/documentation-index.md +12 -18
  73. package/docs/getting-started/documentation-index.md +1 -1
  74. package/docs/getting-started/examples/README.md +4 -4
  75. package/docs/getting-started/examples/full-featured-app.md +1 -1
  76. package/docs/getting-started/faq.md +2 -2
  77. package/docs/getting-started/quick-reference.md +4 -4
  78. package/docs/implementation-guides/authentication.md +15 -15
  79. package/docs/implementation-guides/component-styling.md +1 -1
  80. package/docs/implementation-guides/data-tables.md +126 -33
  81. package/docs/implementation-guides/datatable-rbac-usage.md +1 -1
  82. package/docs/implementation-guides/dynamic-colors.md +3 -3
  83. package/docs/implementation-guides/file-upload-storage.md +2 -2
  84. package/docs/implementation-guides/hierarchical-datatable.md +40 -60
  85. package/docs/implementation-guides/inactivity-tracking.md +3 -3
  86. package/docs/implementation-guides/large-datasets.md +3 -2
  87. package/docs/implementation-guides/organisation-security.md +2 -2
  88. package/docs/implementation-guides/performance.md +2 -2
  89. package/docs/implementation-guides/permission-enforcement.md +1 -1
  90. package/docs/migration/V0.3.44_organisation-context-timing-fix.md +1 -1
  91. package/docs/migration/V0.4.0_rbac-migration.md +6 -6
  92. package/docs/rbac/README.md +5 -5
  93. package/docs/rbac/advanced-patterns.md +6 -6
  94. package/docs/rbac/api-reference.md +20 -20
  95. package/docs/rbac/event-based-apps.md +3 -3
  96. package/docs/rbac/examples.md +41 -41
  97. package/docs/rbac/getting-started.md +37 -37
  98. package/docs/rbac/performance.md +1 -1
  99. package/docs/rbac/quick-start.md +52 -52
  100. package/docs/rbac/secure-client-protection.md +1 -1
  101. package/docs/rbac/troubleshooting.md +1 -1
  102. package/docs/security/README.md +5 -5
  103. package/docs/standards/0-standards-overview.md +220 -0
  104. package/docs/standards/{00-pace-core-compliance.md → 1-pace-core-compliance-standards.md} +204 -185
  105. package/docs/standards/{02-project-structure.md → 2-project-structure-standards.md} +11 -47
  106. package/docs/standards/3-architecture-standards.md +606 -0
  107. package/docs/standards/4-code-quality-standards.md +728 -0
  108. package/docs/standards/{08-markup-quality.md → 5-styling-standards.md} +12 -9
  109. package/docs/standards/{09-rbac-compliance.md → 6-security-rbac-standards.md} +126 -18
  110. package/docs/standards/7-api-tech-stack-standards.md +662 -0
  111. package/docs/standards/8-testing-documentation-standards.md +401 -0
  112. package/docs/standards/9-operations-standards.md +1102 -0
  113. package/docs/standards/README.md +203 -104
  114. package/docs/troubleshooting/README.md +4 -4
  115. package/docs/troubleshooting/common-issues.md +2 -2
  116. package/docs/troubleshooting/debugging.md +9 -9
  117. package/docs/troubleshooting/migration.md +4 -4
  118. package/eslint-config-pace-core.cjs +21 -10
  119. package/package.json +6 -5
  120. package/scripts/install-cursor-rules.cjs +11 -243
  121. package/scripts/install-eslint-config.cjs +284 -0
  122. package/src/__tests__/helpers/__tests__/component-test-utils.test.tsx +2 -2
  123. package/src/__tests__/helpers/__tests__/test-providers.test.tsx +2 -2
  124. package/src/__tests__/helpers/__tests__/test-utils.test.tsx +10 -10
  125. package/src/__tests__/integration/UserProfile.test.tsx +14 -14
  126. package/src/__tests__/rbac/PagePermissionGuard.test.tsx +6 -6
  127. package/src/__tests__/templates/accessibility.test.template.tsx +9 -9
  128. package/src/__tests__/templates/component.test.template.tsx +18 -15
  129. package/src/components/Calendar/Calendar.tsx +201 -47
  130. package/src/components/ContextSelector/ContextSelector.tsx +137 -153
  131. package/src/components/DataTable/AUDIT_REPORT.md +293 -0
  132. package/src/components/DataTable/__tests__/DataTableCore.test.tsx +10 -2
  133. package/src/components/DataTable/__tests__/a11y.basic.test.tsx +10 -4
  134. package/src/components/DataTable/__tests__/test-utils/sharedTestUtils.tsx +9 -9
  135. package/src/components/DataTable/components/ColumnFilter.tsx +63 -74
  136. package/src/components/DataTable/components/ColumnVisibilityDropdown.tsx +43 -41
  137. package/src/components/DataTable/components/DataTableErrorBoundary.tsx +9 -11
  138. package/src/components/DataTable/components/DataTableLayout.tsx +5 -16
  139. package/src/components/DataTable/components/EditableRow.tsx +5 -7
  140. package/src/components/DataTable/components/EmptyState.tsx +10 -9
  141. package/src/components/DataTable/components/FilterRow.tsx +2 -4
  142. package/src/components/DataTable/components/ImportModal.tsx +124 -126
  143. package/src/components/DataTable/components/LoadingState.tsx +5 -6
  144. package/src/components/DataTable/components/SortIndicator.tsx +50 -0
  145. package/src/components/DataTable/components/__tests__/COVERAGE_NOTE.md +4 -4
  146. package/src/components/DataTable/components/__tests__/ColumnFilter.test.tsx +23 -82
  147. package/src/components/DataTable/components/__tests__/DataTableErrorBoundary.test.tsx +37 -9
  148. package/src/components/DataTable/components/__tests__/EmptyState.test.tsx +7 -4
  149. package/src/components/DataTable/components/__tests__/FilterRow.test.tsx +12 -4
  150. package/src/components/DataTable/components/__tests__/LoadingState.test.tsx +41 -27
  151. package/src/components/DataTable/components/index.ts +2 -1
  152. package/src/components/DataTable/types.ts +0 -18
  153. package/src/components/DataTable/utils/a11yUtils.ts +17 -0
  154. package/src/components/DatePickerWithTimezone/DatePickerWithTimezone.test.tsx +2 -1
  155. package/src/components/DatePickerWithTimezone/DatePickerWithTimezone.tsx +11 -15
  156. package/src/components/DateTimeField/DateTimeField.tsx +7 -8
  157. package/src/components/Dialog/Dialog.test.tsx +1 -0
  158. package/src/components/Dialog/Dialog.tsx +25 -8
  159. package/src/components/ErrorBoundary/ErrorBoundary.tsx +77 -79
  160. package/src/components/FileUpload/FileUpload.test.tsx +52 -14
  161. package/src/components/FileUpload/FileUpload.tsx +112 -130
  162. package/src/components/Progress/Progress.tsx +2 -4
  163. package/src/components/ProtectedRoute/ProtectedRoute.tsx +8 -8
  164. package/src/components/Select/Select.tsx +86 -77
  165. package/src/components/Select/types.ts +3 -0
  166. package/src/hooks/__tests__/ServiceHooks.test.tsx +16 -16
  167. package/src/hooks/__tests__/hooks.integration.test.tsx +49 -49
  168. package/src/hooks/__tests__/useFocusTrap.unit.test.tsx +97 -97
  169. package/src/hooks/public/usePublicEvent.ts +5 -5
  170. package/src/hooks/public/usePublicEventLogo.ts +5 -5
  171. package/src/hooks/public/usePublicFileDisplay.ts +2 -2
  172. package/src/hooks/public/usePublicRouteParams.ts +5 -5
  173. package/src/hooks/useAppConfig.ts +2 -2
  174. package/src/hooks/useEventTheme.test.ts +7 -7
  175. package/src/hooks/useEventTheme.ts +1 -4
  176. package/src/hooks/useFileDisplay.ts +2 -2
  177. package/src/providers/UnifiedAuthProvider.smoke.test.tsx +21 -21
  178. package/src/providers/__tests__/AuthProvider.test.tsx +21 -21
  179. package/src/providers/__tests__/EventProvider.test.tsx +61 -61
  180. package/src/providers/__tests__/InactivityProvider.test.tsx +56 -56
  181. package/src/providers/__tests__/OrganisationProvider.test.tsx +75 -75
  182. package/src/providers/__tests__/ProviderLifecycle.test.tsx +37 -37
  183. package/src/providers/__tests__/UnifiedAuthProvider.test.tsx +103 -103
  184. package/src/providers/services/__tests__/AuthServiceProvider.integration.test.tsx +7 -7
  185. package/src/providers/services/__tests__/UnifiedAuthProvider.integration.test.tsx +10 -10
  186. package/src/styles/core.css +7 -0
  187. package/src/theming/__tests__/parseEventColours.test.ts +9 -3
  188. package/src/theming/parseEventColours.ts +22 -10
  189. package/src/utils/__tests__/lazyLoad.unit.test.tsx +42 -39
  190. package/src/utils/storage/README.md +1 -1
  191. package/cursor-rules/01-standards-compliance.mdc +0 -285
  192. package/cursor-rules/04-testing-standards.mdc +0 -270
  193. package/cursor-rules/05-bug-reports-and-features.mdc +0 -248
  194. package/cursor-rules/06-code-quality.mdc +0 -311
  195. package/cursor-rules/07-tech-stack-compliance.mdc +0 -216
  196. package/cursor-rules/10-error-handling-patterns.mdc +0 -179
  197. package/cursor-rules/11-performance-optimization.mdc +0 -169
  198. package/cursor-rules/12-ci-cd-integration.mdc +0 -150
  199. package/dist/DataTable-LRJL4IRV.js +0 -15
  200. package/dist/eslint-rules/rules/compliance.cjs +0 -348
  201. package/dist/eslint-rules/rules/components.cjs +0 -113
  202. package/dist/eslint-rules/rules/imports.cjs +0 -102
  203. package/docs/best-practices/README.md +0 -472
  204. package/docs/best-practices/accessibility.md +0 -604
  205. package/docs/best-practices/common-patterns.md +0 -516
  206. package/docs/best-practices/deployment.md +0 -1103
  207. package/docs/best-practices/performance.md +0 -1328
  208. package/docs/best-practices/security.md +0 -940
  209. package/docs/best-practices/testing.md +0 -1034
  210. package/docs/rbac/compliance/compliance-guide.md +0 -544
  211. package/docs/standards/01-standards-compliance.md +0 -188
  212. package/docs/standards/03-solid-principles.md +0 -39
  213. package/docs/standards/04-testing-standards.md +0 -36
  214. package/docs/standards/05-bug-reports-and-features.md +0 -27
  215. package/docs/standards/06-code-quality.md +0 -34
  216. package/docs/standards/07-tech-stack-compliance.md +0 -30
  217. package/docs/standards/10-error-handling-patterns.md +0 -401
  218. package/docs/standards/11-performance-optimization.md +0 -348
  219. package/docs/standards/12-ci-cd-integration.md +0 -370
  220. package/docs/standards/ALIGNMENT_REVIEW_SUMMARY.md +0 -192
  221. package/scripts/audit/audit-compliance.cjs +0 -1295
  222. package/scripts/audit/audit-components.cjs +0 -260
  223. package/scripts/audit/audit-rbac.cjs +0 -954
  224. package/scripts/audit/audit-standards.cjs +0 -1268
  225. package/scripts/audit/index.cjs +0 -1927
  226. package/src/components/DataTable/components/DataTableBody.tsx +0 -478
  227. package/src/components/DataTable/components/DraggableColumnHeader.tsx +0 -156
  228. package/src/components/DataTable/components/ExpandButton.tsx +0 -113
  229. package/src/components/DataTable/components/GroupHeader.tsx +0 -54
  230. package/src/components/DataTable/components/ViewRowModal.tsx +0 -68
  231. package/src/components/DataTable/components/VirtualizedDataTable.tsx +0 -525
  232. package/src/components/DataTable/components/__tests__/ExpandButton.test.tsx +0 -462
  233. package/src/components/DataTable/components/__tests__/GroupHeader.test.tsx +0 -393
  234. package/src/components/DataTable/components/__tests__/ViewRowModal.test.tsx +0 -476
  235. package/src/components/DataTable/components/__tests__/VirtualizedDataTable.test.tsx +0 -128
  236. package/src/components/DataTable/core/DataTableContext.tsx +0 -216
  237. package/src/components/DataTable/core/__tests__/DataTableContext.test.tsx +0 -136
  238. package/src/components/DataTable/hooks/__tests__/useColumnReordering.test.ts +0 -570
  239. package/src/components/DataTable/hooks/useColumnReordering.ts +0 -123
  240. package/src/components/DataTable/utils/debugTools.ts +0 -514
  241. package/src/eslint-rules/index.cjs +0 -22
  242. package/src/eslint-rules/rules/components.cjs +0 -113
  243. package/src/eslint-rules/rules/imports.cjs +0 -102
  244. package/src/eslint-rules/rules/rbac.cjs +0 -790
  245. package/src/eslint-rules/utils/helpers.cjs +0 -42
  246. package/src/eslint-rules/utils/manifest-loader.cjs +0 -75
@@ -1,21 +1,20 @@
1
1
  #!/usr/bin/env node
2
2
 
3
3
  /**
4
- * Installation Script for pace-core Cursor Rules and ESLint Config
4
+ * Installation Script for pace-core Cursor Rules
5
5
  * @package @jmruthers/pace-core
6
6
  * @module Scripts/install-cursor-rules
7
7
  *
8
- * Sets up both:
9
- * 1. Cursor rules - Copies cursor rules from pace-core to consuming app's .cursor/rules/ directory
10
- * 2. ESLint config - Adds pace-core ESLint config to consuming app's ESLint configuration
8
+ * Sets up cursor rules:
9
+ * - Copies cursor rules from pace-core to consuming app's .cursor/rules/ directory
11
10
  *
12
11
  * This is an opt-in script - it does NOT run automatically via postinstall.
13
12
  *
13
+ * For ESLint setup, use install-eslint-config.cjs separately.
14
+ *
14
15
  * Usage:
15
- * node install-cursor-rules.cjs # Install both cursor rules and ESLint
16
- * node install-cursor-rules.cjs --skip-eslint # Only install cursor rules
17
- * node install-cursor-rules.cjs --skip-cursor # Only setup ESLint
18
- * node install-cursor-rules.cjs --force # Force update even if already configured
16
+ * node install-cursor-rules.cjs # Install cursor rules
17
+ * node install-cursor-rules.cjs --force # Force update even if already configured
19
18
  */
20
19
 
21
20
  const fs = require('fs');
@@ -219,250 +218,21 @@ function installCursorRules(force = false) {
219
218
  console.log(`${colors.cyan}Restart Cursor to load the new rules.${colors.reset}`);
220
219
  }
221
220
 
222
- // Find existing ESLint config file
223
- function findESLintConfig() {
224
- const cwd = process.cwd();
225
- const possibleConfigs = [
226
- 'eslint.config.js',
227
- 'eslint.config.cjs',
228
- 'eslint.config.mjs',
229
- '.eslintrc.js',
230
- '.eslintrc.cjs',
231
- '.eslintrc.json',
232
- '.eslintrc.yaml',
233
- '.eslintrc.yml',
234
- ];
235
-
236
- for (const configFile of possibleConfigs) {
237
- const configPath = path.join(cwd, configFile);
238
- if (fs.existsSync(configPath)) {
239
- return { path: configPath, name: configFile, isESM: configFile.endsWith('.js') || configFile.endsWith('.mjs') };
240
- }
241
- }
242
-
243
- return null;
244
- }
245
-
246
- // Check if pace-core config is already included
247
- function hasPaceCoreConfig(configContent) {
248
- // Check for pace-core config import/require
249
- const paceCorePatterns = [
250
- /@jmruthers\/pace-core\/eslint-config/,
251
- /pace-core\/eslint-config/,
252
- /paceCoreConfig/,
253
- /pace-core-compliance/,
254
- ];
255
-
256
- return paceCorePatterns.some(pattern => pattern.test(configContent));
257
- }
258
-
259
- // Detect if file is ES module
260
- function isESModule(filePath, content) {
261
- if (filePath.endsWith('.mjs')) return true;
262
- if (filePath.endsWith('.cjs')) return false;
263
- if (filePath.endsWith('.js')) {
264
- // Check for ES module indicators
265
- return /^import\s+.*from|^export\s+default/.test(content.trim());
266
- }
267
- return false;
268
- }
269
-
270
- // Backup file before modification
271
- function backupFile(filePath) {
272
- const backupPath = `${filePath}.backup.${Date.now()}`;
273
- fs.copyFileSync(filePath, backupPath);
274
- return backupPath;
275
- }
276
-
277
- // Setup ESLint configuration
278
- function setupESLintConfig(force = false, skipIfExists = false) {
279
- const cwd = process.cwd();
280
- const existingConfig = findESLintConfig();
281
-
282
- if (existingConfig) {
283
- // Read existing config
284
- let content = fs.readFileSync(existingConfig.path, 'utf8');
285
- const isESM = isESModule(existingConfig.path, content);
286
- const format = isESM ? 'ES Module' : 'CommonJS';
287
-
288
- console.log(`${colors.cyan}Found ESLint config:${colors.reset} ${existingConfig.name} (${format})`);
289
-
290
- // Check if already configured
291
- if (hasPaceCoreConfig(content)) {
292
- if (skipIfExists && !force) {
293
- console.log(`${colors.blue}○${colors.reset} ${existingConfig.name} already includes pace-core rules`);
294
- return { action: 'skipped', file: existingConfig.name, format };
295
- }
296
-
297
- if (force) {
298
- console.log(`${colors.yellow}Updating existing ESLint config...${colors.reset}`);
299
- } else {
300
- console.log(`${colors.blue}○${colors.reset} ${existingConfig.name} already configured. Use --force to update.`);
301
- return { action: 'skipped', file: existingConfig.name, format };
302
- }
303
- } else {
304
- console.log(`${colors.cyan}Adding pace-core config to ${existingConfig.name}...${colors.reset}`);
305
- }
306
-
307
- // Backup before modification
308
- const backupPath = backupFile(existingConfig.path);
309
- console.log(`${colors.cyan} Backed up to ${path.basename(backupPath)}${colors.reset}`);
310
-
311
- // Add pace-core config
312
- if (isESM) {
313
- // ES Module format
314
- if (!content.includes('import paceCoreConfig')) {
315
- // Add import at top (after other imports if they exist)
316
- const importLines = content.match(/^(import\s+[^;]+;?\s*\n)+/m);
317
- if (importLines) {
318
- // Add after existing imports
319
- content = content.replace(
320
- importLines[0],
321
- `${importLines[0]}import paceCoreConfig from '@jmruthers/pace-core/eslint-config';\n`
322
- );
323
- } else {
324
- // Add at the beginning
325
- content = `import paceCoreConfig from '@jmruthers/pace-core/eslint-config';\n${content}`;
326
- }
327
- }
328
-
329
- // Add to export default array
330
- if (content.includes('export default [')) {
331
- // Already an array, add paceCoreConfig at the beginning
332
- if (!content.includes('...paceCoreConfig')) {
333
- content = content.replace(
334
- /(export\s+default\s*\[)\s*/,
335
- '$1\n ...paceCoreConfig,'
336
- );
337
- }
338
- } else if (content.includes('export default')) {
339
- // Not an array, wrap it
340
- const exportMatch = content.match(/(export\s+default\s+)(.+?)(;?\s*$)/s);
341
- if (exportMatch) {
342
- content = content.replace(
343
- exportMatch[0],
344
- `${exportMatch[1]}[\n ...paceCoreConfig,\n ${exportMatch[2]}\n];`
345
- );
346
- }
347
- }
348
- } else {
349
- // CommonJS format
350
- if (!content.includes('require(\'@jmruthers/pace-core/eslint-config\')')) {
351
- // Add require at top (after other requires if they exist)
352
- const requireLines = content.match(/^(const\s+\w+\s*=\s*require\([^)]+\);\s*\n)+/m);
353
- if (requireLines) {
354
- content = content.replace(
355
- requireLines[0],
356
- `${requireLines[0]}const paceCoreConfig = require('@jmruthers/pace-core/eslint-config');\n`
357
- );
358
- } else {
359
- content = `const paceCoreConfig = require('@jmruthers/pace-core/eslint-config');\n${content}`;
360
- }
361
- }
362
-
363
- // Add to module.exports
364
- if (content.includes('module.exports = [')) {
365
- // Already an array, add paceCoreConfig at the beginning
366
- if (!content.includes('...paceCoreConfig')) {
367
- content = content.replace(
368
- /(module\.exports\s*=\s*\[)\s*/,
369
- '$1\n ...paceCoreConfig,'
370
- );
371
- }
372
- } else if (content.includes('module.exports =')) {
373
- // Not an array, wrap it
374
- const exportMatch = content.match(/(module\.exports\s*=\s*)(.+?)(;?\s*$)/s);
375
- if (exportMatch) {
376
- content = content.replace(
377
- exportMatch[0],
378
- `${exportMatch[1]}[\n ...paceCoreConfig,\n ${exportMatch[2]}\n];`
379
- );
380
- }
381
- }
382
- }
383
-
384
- // Write updated config
385
- fs.writeFileSync(existingConfig.path, content, 'utf8');
386
- console.log(`${colors.green}✓${colors.reset} Updated ${existingConfig.name}`);
387
- return { action: 'updated', file: existingConfig.name, backup: backupPath, format };
388
- } else {
389
- // Create new ESLint config (default to ES modules)
390
- const configPath = path.join(cwd, 'eslint.config.js');
391
- const configContent = `import paceCoreConfig from '@jmruthers/pace-core/eslint-config';
392
-
393
- export default [
394
- ...paceCoreConfig,
395
- // Your app-specific ESLint configuration
396
- {
397
- // Add your rules here
398
- },
399
- ];
400
- `;
401
-
402
- console.log(`${colors.cyan}No ESLint config found. Creating eslint.config.js...${colors.reset}`);
403
- fs.writeFileSync(configPath, configContent, 'utf8');
404
- console.log(`${colors.green}✓${colors.reset} Created eslint.config.js`);
405
- console.log(`${colors.cyan} Edit eslint.config.js to add your app-specific ESLint rules.${colors.reset}`);
406
- return { action: 'created', file: 'eslint.config.js', format: 'ES Module' };
407
- }
408
- }
409
221
 
410
222
  // Main execution
411
223
  function main() {
412
224
  const force = process.argv.includes('--force');
413
- const skipCursor = process.argv.includes('--skip-cursor');
414
- const skipESLint = process.argv.includes('--skip-eslint');
415
225
 
416
226
  if (force) {
417
227
  console.log(`${colors.yellow}Warning: --force flag is set. This will overwrite existing configurations.${colors.reset}\n`);
418
228
  }
419
229
 
420
230
  try {
421
- let cursorResult = null;
422
- let eslintResult = null;
423
-
424
- // Install cursor rules
425
- if (!skipCursor) {
426
- installCursorRules(force);
427
- cursorResult = { completed: true };
428
- } else {
429
- console.log(`${colors.blue}Skipping cursor rules installation (--skip-cursor)${colors.reset}\n`);
430
- }
431
-
432
- // Setup ESLint config
433
- if (!skipESLint) {
434
- console.log(`\n${colors.cyan}Setting up ESLint configuration...${colors.reset}\n`);
435
- eslintResult = setupESLintConfig(force, true);
436
-
437
- // Output is handled in setupESLintConfig, but ensure we have consistent formatting
438
- } else {
439
- console.log(`\n${colors.blue}Skipping ESLint setup (--skip-eslint)${colors.reset}`);
440
- }
441
-
442
- // Summary
443
- console.log(`\n${colors.bold}Setup Summary:${colors.reset}`);
444
- if (cursorResult) {
445
- console.log(` ${colors.green}Cursor Rules:${colors.reset} Installed`);
446
- }
447
- if (eslintResult) {
448
- if (eslintResult.action === 'created') {
449
- console.log(` ${colors.green}ESLint Config:${colors.reset} Created ${eslintResult.file} (${eslintResult.format})`);
450
- } else if (eslintResult.action === 'updated') {
451
- console.log(` ${colors.green}ESLint Config:${colors.reset} Updated ${eslintResult.file} (${eslintResult.format})`);
452
- console.log(` ${colors.yellow}Backup:${colors.reset} ${path.basename(eslintResult.backup)}`);
453
- } else {
454
- console.log(` ${colors.blue}ESLint Config:${colors.reset} Already configured (${eslintResult.file}, ${eslintResult.format})`);
455
- }
456
- }
231
+ installCursorRules(force);
457
232
 
458
233
  console.log(`\n${colors.cyan}Next steps:${colors.reset}`);
459
- if (!skipCursor) {
460
- console.log(` • Restart Cursor to load the new rules`);
461
- }
462
- if (!skipESLint) {
463
- console.log(` • Run ${colors.bold}npm run lint${colors.reset} to verify ESLint is working`);
464
- console.log(` • Edit your ESLint config to add app-specific rules`);
465
- }
234
+ console.log(` • Restart Cursor to load the new rules`);
235
+ console.log(` • For ESLint setup, run: ${colors.bold}node node_modules/@jmruthers/pace-core/scripts/install-eslint-config.cjs${colors.reset}`);
466
236
 
467
237
  } catch (error) {
468
238
  console.error(`${colors.red}Error during setup:${colors.reset}`);
@@ -481,7 +251,5 @@ if (require.main === module) {
481
251
 
482
252
  module.exports = {
483
253
  installCursorRules,
484
- getCursorRulesTarget,
485
- setupESLintConfig,
486
- findESLintConfig
254
+ getCursorRulesTarget
487
255
  };
@@ -0,0 +1,284 @@
1
+ #!/usr/bin/env node
2
+
3
+ /**
4
+ * Installation Script for pace-core ESLint Config
5
+ * @package @jmruthers/pace-core
6
+ * @module Scripts/install-eslint-config
7
+ *
8
+ * Sets up pace-core ESLint configuration in consuming apps.
9
+ * Adds pace-core ESLint config to consuming app's ESLint configuration.
10
+ *
11
+ * This is an opt-in script - it does NOT run automatically via postinstall.
12
+ *
13
+ * Usage:
14
+ * node install-eslint-config.cjs # Setup ESLint config
15
+ * node install-eslint-config.cjs --force # Force update even if already configured
16
+ */
17
+
18
+ const fs = require('fs');
19
+ const path = require('path');
20
+
21
+ // Check for safety guards
22
+ function checkSafetyGuards() {
23
+ // Check environment variable
24
+ if (process.env.PACE_ESLINT_CONFIG_DISABLED === '1') {
25
+ console.log(`${colors.yellow}Skipping ESLint config installation: PACE_ESLINT_CONFIG_DISABLED=1${colors.reset}`);
26
+ process.exit(0);
27
+ }
28
+
29
+ // Check for .git folder (safety guard for CI)
30
+ const cwd = process.cwd();
31
+ if (!fs.existsSync(path.join(cwd, '.git'))) {
32
+ console.log(`${colors.yellow}Skipping ESLint config installation: No .git folder found${colors.reset}`);
33
+ console.log(`${colors.yellow}This is a safety guard. If you want to install anyway, run with --force${colors.reset}`);
34
+ if (!process.argv.includes('--force')) {
35
+ process.exit(0);
36
+ }
37
+ }
38
+ }
39
+
40
+ // ANSI color codes for terminal output
41
+ const colors = {
42
+ reset: '\x1b[0m',
43
+ green: '\x1b[32m',
44
+ yellow: '\x1b[33m',
45
+ blue: '\x1b[34m',
46
+ cyan: '\x1b[36m',
47
+ bold: '\x1b[1m',
48
+ red: '\x1b[31m'
49
+ };
50
+
51
+ // Find existing ESLint config file
52
+ function findESLintConfig() {
53
+ const cwd = process.cwd();
54
+ const possibleConfigs = [
55
+ 'eslint.config.js',
56
+ 'eslint.config.cjs',
57
+ 'eslint.config.mjs',
58
+ '.eslintrc.js',
59
+ '.eslintrc.cjs',
60
+ '.eslintrc.json',
61
+ '.eslintrc.yaml',
62
+ '.eslintrc.yml',
63
+ ];
64
+
65
+ for (const configFile of possibleConfigs) {
66
+ const configPath = path.join(cwd, configFile);
67
+ if (fs.existsSync(configPath)) {
68
+ return { path: configPath, name: configFile, isESM: configFile.endsWith('.js') || configFile.endsWith('.mjs') };
69
+ }
70
+ }
71
+
72
+ return null;
73
+ }
74
+
75
+ // Check if pace-core config is already included
76
+ function hasPaceCoreConfig(configContent) {
77
+ // Check for pace-core config import/require
78
+ const paceCorePatterns = [
79
+ /@jmruthers\/pace-core\/eslint-config/,
80
+ /pace-core\/eslint-config/,
81
+ /paceCoreConfig/,
82
+ /pace-core-compliance/,
83
+ ];
84
+
85
+ return paceCorePatterns.some(pattern => pattern.test(configContent));
86
+ }
87
+
88
+ // Detect if file is ES module
89
+ function isESModule(filePath, content) {
90
+ if (filePath.endsWith('.mjs')) return true;
91
+ if (filePath.endsWith('.cjs')) return false;
92
+ if (filePath.endsWith('.js')) {
93
+ // Check for ES module indicators
94
+ return /^import\s+.*from|^export\s+default/.test(content.trim());
95
+ }
96
+ return false;
97
+ }
98
+
99
+ // Backup file before modification
100
+ function backupFile(filePath) {
101
+ const backupPath = `${filePath}.backup.${Date.now()}`;
102
+ fs.copyFileSync(filePath, backupPath);
103
+ return backupPath;
104
+ }
105
+
106
+ // Setup ESLint configuration
107
+ function setupESLintConfig(force = false) {
108
+ const cwd = process.cwd();
109
+ const existingConfig = findESLintConfig();
110
+
111
+ if (existingConfig) {
112
+ // Read existing config
113
+ let content = fs.readFileSync(existingConfig.path, 'utf8');
114
+ const isESM = isESModule(existingConfig.path, content);
115
+ const format = isESM ? 'ES Module' : 'CommonJS';
116
+
117
+ console.log(`${colors.cyan}Found ESLint config:${colors.reset} ${existingConfig.name} (${format})`);
118
+
119
+ // Check if already configured
120
+ if (hasPaceCoreConfig(content)) {
121
+ if (!force) {
122
+ console.log(`${colors.blue}○${colors.reset} ${existingConfig.name} already includes pace-core rules`);
123
+ console.log(`${colors.blue} Use --force to update.${colors.reset}`);
124
+ return { action: 'skipped', file: existingConfig.name, format };
125
+ }
126
+
127
+ console.log(`${colors.yellow}Updating existing ESLint config...${colors.reset}`);
128
+ } else {
129
+ console.log(`${colors.cyan}Adding pace-core config to ${existingConfig.name}...${colors.reset}`);
130
+ }
131
+
132
+ // Backup before modification
133
+ const backupPath = backupFile(existingConfig.path);
134
+ console.log(`${colors.cyan} Backed up to ${path.basename(backupPath)}${colors.reset}`);
135
+
136
+ // Add pace-core config
137
+ if (isESM) {
138
+ // ES Module format
139
+ if (!content.includes('import paceCoreConfig')) {
140
+ // Add import at top (after other imports if they exist)
141
+ const importLines = content.match(/^(import\s+[^;]+;?\s*\n)+/m);
142
+ if (importLines) {
143
+ // Add after existing imports
144
+ content = content.replace(
145
+ importLines[0],
146
+ `${importLines[0]}import paceCoreConfig from '@jmruthers/pace-core/eslint-config';\n`
147
+ );
148
+ } else {
149
+ // Add at the beginning
150
+ content = `import paceCoreConfig from '@jmruthers/pace-core/eslint-config';\n${content}`;
151
+ }
152
+ }
153
+
154
+ // Add to export default array
155
+ if (content.includes('export default [')) {
156
+ // Already an array, add paceCoreConfig at the beginning
157
+ if (!content.includes('...paceCoreConfig')) {
158
+ content = content.replace(
159
+ /(export\s+default\s*\[)\s*/,
160
+ '$1\n ...paceCoreConfig,'
161
+ );
162
+ }
163
+ } else if (content.includes('export default')) {
164
+ // Not an array, wrap it
165
+ const exportMatch = content.match(/(export\s+default\s+)(.+?)(;?\s*$)/s);
166
+ if (exportMatch) {
167
+ content = content.replace(
168
+ exportMatch[0],
169
+ `${exportMatch[1]}[\n ...paceCoreConfig,\n ${exportMatch[2]}\n];`
170
+ );
171
+ }
172
+ }
173
+ } else {
174
+ // CommonJS format
175
+ if (!content.includes('require(\'@jmruthers/pace-core/eslint-config\')')) {
176
+ // Add require at top (after other requires if they exist)
177
+ const requireLines = content.match(/^(const\s+\w+\s*=\s*require\([^)]+\);\s*\n)+/m);
178
+ if (requireLines) {
179
+ content = content.replace(
180
+ requireLines[0],
181
+ `${requireLines[0]}const paceCoreConfig = require('@jmruthers/pace-core/eslint-config');\n`
182
+ );
183
+ } else {
184
+ content = `const paceCoreConfig = require('@jmruthers/pace-core/eslint-config');\n${content}`;
185
+ }
186
+ }
187
+
188
+ // Add to module.exports
189
+ if (content.includes('module.exports = [')) {
190
+ // Already an array, add paceCoreConfig at the beginning
191
+ if (!content.includes('...paceCoreConfig')) {
192
+ content = content.replace(
193
+ /(module\.exports\s*=\s*\[)\s*/,
194
+ '$1\n ...paceCoreConfig,'
195
+ );
196
+ }
197
+ } else if (content.includes('module.exports =')) {
198
+ // Not an array, wrap it
199
+ const exportMatch = content.match(/(module\.exports\s*=\s*)(.+?)(;?\s*$)/s);
200
+ if (exportMatch) {
201
+ content = content.replace(
202
+ exportMatch[0],
203
+ `${exportMatch[1]}[\n ...paceCoreConfig,\n ${exportMatch[2]}\n];`
204
+ );
205
+ }
206
+ }
207
+ }
208
+
209
+ // Write updated config
210
+ fs.writeFileSync(existingConfig.path, content, 'utf8');
211
+ console.log(`${colors.green}✓${colors.reset} Updated ${existingConfig.name}`);
212
+ return { action: 'updated', file: existingConfig.name, backup: backupPath, format };
213
+ } else {
214
+ // Create new ESLint config (default to ES modules)
215
+ const configPath = path.join(cwd, 'eslint.config.js');
216
+ const configContent = `import paceCoreConfig from '@jmruthers/pace-core/eslint-config';
217
+
218
+ export default [
219
+ ...paceCoreConfig,
220
+ // Your app-specific ESLint configuration
221
+ {
222
+ // Add your rules here
223
+ },
224
+ ];
225
+ `;
226
+
227
+ console.log(`${colors.cyan}No ESLint config found. Creating eslint.config.js...${colors.reset}`);
228
+ fs.writeFileSync(configPath, configContent, 'utf8');
229
+ console.log(`${colors.green}✓${colors.reset} Created eslint.config.js`);
230
+ console.log(`${colors.cyan} Edit eslint.config.js to add your app-specific ESLint rules.${colors.reset}`);
231
+ return { action: 'created', file: 'eslint.config.js', format: 'ES Module' };
232
+ }
233
+ }
234
+
235
+ // Main execution
236
+ function main() {
237
+ checkSafetyGuards();
238
+
239
+ const force = process.argv.includes('--force');
240
+
241
+ if (force) {
242
+ console.log(`${colors.yellow}Warning: --force flag is set. This will overwrite existing configurations.${colors.reset}\n`);
243
+ }
244
+
245
+ try {
246
+ console.log(`${colors.cyan}Setting up ESLint configuration from pace-core...${colors.reset}\n`);
247
+ const result = setupESLintConfig(force);
248
+
249
+ // Summary
250
+ console.log(`\n${colors.bold}Setup Summary:${colors.reset}`);
251
+ if (result.action === 'created') {
252
+ console.log(` ${colors.green}ESLint Config:${colors.reset} Created ${result.file} (${result.format})`);
253
+ } else if (result.action === 'updated') {
254
+ console.log(` ${colors.green}ESLint Config:${colors.reset} Updated ${result.file} (${result.format})`);
255
+ console.log(` ${colors.yellow}Backup:${colors.reset} ${path.basename(result.backup)}`);
256
+ } else {
257
+ console.log(` ${colors.blue}ESLint Config:${colors.reset} Already configured (${result.file}, ${result.format})`);
258
+ }
259
+
260
+ console.log(`\n${colors.cyan}ESLint configuration is now available${colors.reset}`);
261
+ console.log(`\n${colors.cyan}Next steps:${colors.reset}`);
262
+ console.log(` • Run ${colors.bold}npm run lint${colors.reset} to verify ESLint is working`);
263
+ console.log(` • Edit your ESLint config to add app-specific rules`);
264
+ console.log(` • See ${colors.bold}packages/core/docs/standards/${colors.reset} for complete standards documentation`);
265
+
266
+ } catch (error) {
267
+ console.error(`${colors.red}Error during setup:${colors.reset}`);
268
+ console.error(error.message);
269
+ if (error.stack) {
270
+ console.error(error.stack);
271
+ }
272
+ process.exit(1);
273
+ }
274
+ }
275
+
276
+ // Run if called directly
277
+ if (require.main === module) {
278
+ main();
279
+ }
280
+
281
+ module.exports = {
282
+ setupESLintConfig,
283
+ findESLintConfig
284
+ };
@@ -23,7 +23,7 @@ vi.mock('../test-utils', () => ({
23
23
  describe('[helpers] componentTestPatterns', () => {
24
24
  describe('testRenders', () => {
25
25
  it('creates test that renders component without crashing', () => {
26
- const TestComponent = () => <div data-testid="test-component">Test</div>;
26
+ const TestComponent = () => <p data-testid="test-component">Test</p>;
27
27
  const testFn = componentTestPatterns.testRenders(TestComponent);
28
28
 
29
29
  expect(typeof testFn).toBe('function');
@@ -33,7 +33,7 @@ describe('[helpers] componentTestPatterns', () => {
33
33
  });
34
34
 
35
35
  it('creates test that renders component with props', () => {
36
- const TestComponent = ({ name }: { name: string }) => <div data-testid="test-component">{name}</div>;
36
+ const TestComponent = ({ name }: { name: string }) => <p data-testid="test-component">{name}</p>;
37
37
  const testFn = componentTestPatterns.testRenders(TestComponent, { name: 'Test Name' });
38
38
 
39
39
  expect(typeof testFn).toBe('function');
@@ -14,11 +14,11 @@ import { TestProviderWrapper, renderWithProviders, createMockUseUnifiedAuth, use
14
14
  function TestComponent() {
15
15
  const auth = useUnifiedAuth();
16
16
  return (
17
- <div>
17
+ <p>
18
18
  <span data-testid="user-email">{auth.user?.email}</span>
19
19
  <span data-testid="is-authenticated">{auth.isAuthenticated ? 'true' : 'false'}</span>
20
20
  <span data-testid="app-name">{auth.appName}</span>
21
- </div>
21
+ </p>
22
22
  );
23
23
  }
24
24
 
@@ -41,32 +41,32 @@ vi.mock('../fixtures/test-data', () => ({
41
41
 
42
42
  describe('[helpers] renderWithProviders', () => {
43
43
  it('renders component with QueryClient provider by default', () => {
44
- const TestComponent = () => <div data-testid="test-component">Test</div>;
44
+ const TestComponent = () => <p data-testid="test-component">Test</p>;
45
45
 
46
46
  renderWithProviders(<TestComponent />);
47
47
 
48
- expect(screen.getByTestId('test-component')).toBeInTheDocument();
48
+ expect(screen.getByTestId('test-component')).toBeDefined();
49
49
  });
50
50
 
51
51
  it('renders component without QueryClient when withQueryClient is false', () => {
52
- const TestComponent = () => <div data-testid="test-component">Test</div>;
52
+ const TestComponent = () => <p data-testid="test-component">Test</p>;
53
53
 
54
54
  renderWithProviders(<TestComponent />, { withQueryClient: false });
55
55
 
56
- expect(screen.getByTestId('test-component')).toBeInTheDocument();
56
+ expect(screen.getByTestId('test-component')).toBeDefined();
57
57
  });
58
58
 
59
59
  it('uses custom QueryClient when provided', () => {
60
60
  const customQueryClient = new QueryClient();
61
- const TestComponent = () => <div data-testid="test-component">Test</div>;
61
+ const TestComponent = () => <p data-testid="test-component">Test</p>;
62
62
 
63
63
  renderWithProviders(<TestComponent />, { queryClient: customQueryClient });
64
64
 
65
- expect(screen.getByTestId('test-component')).toBeInTheDocument();
65
+ expect(screen.getByTestId('test-component')).toBeDefined();
66
66
  });
67
67
 
68
68
  it('forwards additional render options', () => {
69
- const TestComponent = () => <div data-testid="test-component">Test</div>;
69
+ const TestComponent = () => <p data-testid="test-component">Test</p>;
70
70
 
71
71
  const { container } = renderWithProviders(<TestComponent />, {
72
72
  container: document.body
@@ -273,7 +273,7 @@ describe('[helpers] createMockSupabaseClient', () => {
273
273
 
274
274
  it('creates mock query builder with chaining methods', () => {
275
275
  const mockClient = createMockSupabaseClient();
276
- const queryBuilder = mockClient.from('test_table');
276
+ const queryBuilder = (mockClient.from as any)('test_table');
277
277
 
278
278
  expect(queryBuilder).toHaveProperty('select');
279
279
  expect(queryBuilder).toHaveProperty('insert');
@@ -302,7 +302,7 @@ describe('[helpers] createMockSupabaseClient', () => {
302
302
 
303
303
  describe('[helpers] TestWrapper', () => {
304
304
  it('renders children with QueryClient provider', () => {
305
- const TestComponent = () => <div data-testid="test-component">Test</div>;
305
+ const TestComponent = () => <p data-testid="test-component">Test</p>;
306
306
 
307
307
  render(
308
308
  <TestWrapper>
@@ -310,7 +310,7 @@ describe('[helpers] TestWrapper', () => {
310
310
  </TestWrapper>
311
311
  );
312
312
 
313
- expect(screen.getByTestId('test-component')).toBeInTheDocument();
313
+ expect(screen.getByTestId('test-component')).toBeDefined();
314
314
  });
315
315
  });
316
316