@jmruthers/pace-core 0.6.7 → 0.6.8
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/audit-tool/00-dependencies.cjs +215 -9
- package/audit-tool/audits/02-project-structure.cjs +3 -18
- package/audit-tool/audits/03-architecture.cjs +34 -6
- package/audit-tool/audits/06-security-rbac.cjs +10 -0
- package/audit-tool/audits/07-api-tech-stack.cjs +55 -1
- package/audit-tool/index.cjs +23 -19
- package/audit-tool/utils/report-utils.cjs +141 -2
- package/dist/{DataTable-7PMH7XN7.js → DataTable-6RMSCQJ6.js} +5 -5
- package/dist/{PublicPageProvider-DlsCaR5v.d.ts → PublicPageProvider-CIGSujI2.d.ts} +14 -8
- package/dist/{UnifiedAuthProvider-ZT6TIGM7.js → UnifiedAuthProvider-7SNDOWYD.js} +2 -2
- package/dist/{api-Y4MQWOFW.js → api-7P7DI652.js} +1 -1
- package/dist/{chunk-L4XMVJKY.js → chunk-4DDCYDQ3.js} +8 -7
- package/dist/{chunk-ZKAWKYT4.js → chunk-5W2A3DRC.js} +2 -1
- package/dist/{chunk-VBCS3DUA.js → chunk-EF2UGZWY.js} +3 -3
- package/dist/{chunk-JGWDVX64.js → chunk-EURB7QFZ.js} +123 -53
- package/dist/{chunk-BM4CQ5P3.js → chunk-GS5672WG.js} +6 -6
- package/dist/{chunk-ZFYPMX46.js → chunk-LX6U42O3.js} +1 -1
- package/dist/{chunk-5X4QLXRG.js → chunk-MPBLMWVR.js} +5 -3
- package/dist/{chunk-Q7Q7V5NV.js → chunk-NKHKXPI4.js} +7 -7
- package/dist/{chunk-6F3IILHI.js → chunk-S6ZQKDY6.js} +1 -1
- package/dist/{chunk-FTCRZOG2.js → chunk-T5CVK4R3.js} +5 -5
- package/dist/{chunk-GHYHJTYV.js → chunk-Z2FNRKF3.js} +13 -13
- package/dist/components.d.ts +1 -1
- package/dist/components.js +12 -12
- package/dist/eslint-rules/rules/04-code-quality.cjs +66 -10
- package/dist/eslint-rules/rules/06-security-rbac.cjs +8 -3
- package/dist/eslint-rules/rules/07-api-tech-stack.cjs +190 -68
- package/dist/{functions-DHebl8-F.d.ts → functions-lBy5L2ry.d.ts} +1 -1
- package/dist/hooks.js +7 -7
- package/dist/index.d.ts +2 -2
- package/dist/index.js +15 -15
- package/dist/providers.js +2 -2
- package/dist/rbac/index.d.ts +1 -1
- package/dist/rbac/index.js +6 -6
- package/dist/theming/runtime.d.ts +48 -1
- package/dist/theming/runtime.js +1 -1
- package/dist/types.d.ts +2 -2
- package/dist/utils.js +1 -1
- package/docs/api/modules.md +63 -14
- package/docs/getting-started/dependencies.md +23 -0
- package/docs/implementation-guides/app-layout.md +1 -1
- package/docs/implementation-guides/data-tables.md +1 -1
- package/docs/standards/1-pace-core-compliance-standards.md +38 -1
- package/eslint-config-pace-core.cjs +30 -11
- package/package.json +45 -15
- package/scripts/eslint-audit.cjs +123 -0
- package/scripts/install-eslint-config.cjs +67 -2
- package/scripts/validate-dependencies.cjs +248 -0
- package/src/__tests__/helpers/__tests__/test-utils.test.tsx +20 -8
- package/src/__tests__/templates/accessibility.test.template.tsx +1 -0
- package/src/components/AddressField/AddressField.tsx +26 -1
- package/src/components/Alert/Alert.test.tsx +86 -22
- package/src/components/Alert/Alert.tsx +19 -11
- package/src/components/Badge/Badge.tsx +1 -1
- package/src/components/Checkbox/Checkbox.test.tsx +2 -1
- package/src/components/ContextSelector/ContextSelector.tsx +39 -41
- package/src/components/DataTable/DataTable.tsx +1 -19
- package/src/components/DataTable/__tests__/DataTableCore.test.tsx +6 -10
- package/src/components/DataTable/__tests__/a11y.basic.test.tsx +18 -9
- package/src/components/DataTable/__tests__/pagination.modes.test.tsx +3 -2
- package/src/components/DataTable/components/EmptyState.tsx +1 -1
- package/src/components/DataTable/components/__tests__/DataTableErrorBoundary.test.tsx +1 -1
- package/src/components/DataTable/components/__tests__/EmptyState.test.tsx +3 -3
- package/src/components/DataTable/components/__tests__/LoadingState.test.tsx +33 -29
- package/src/components/DatePickerWithTimezone/DatePickerWithTimezone.test.tsx +1 -2
- package/src/components/FileUpload/FileUpload.test.tsx +22 -31
- package/src/components/FileUpload/FileUpload.tsx +29 -0
- package/src/components/NavigationMenu/NavigationMenu.test.tsx +48 -12
- package/src/components/PaceAppLayout/PaceAppLayout.performance.test.tsx +9 -9
- package/src/components/PaceAppLayout/PaceAppLayout.security.test.tsx +30 -30
- package/src/components/PaceAppLayout/PaceAppLayout.test.tsx +4 -4
- package/src/components/PaceLoginPage/PaceLoginPage.test.tsx +7 -1
- package/src/hooks/__tests__/useDataTablePerformance.unit.test.ts +8 -5
- package/src/hooks/__tests__/useFileUrl.unit.test.ts +4 -0
- package/src/hooks/__tests__/useFocusTrap.unit.test.tsx +3 -3
- package/src/hooks/__tests__/useInactivityTracker.unit.test.ts +45 -8
- package/src/hooks/__tests__/usePerformanceMonitor.unit.test.ts +22 -2
- package/src/hooks/public/usePublicRouteParams.ts +8 -4
- package/src/hooks/useAddressAutocomplete.test.ts +18 -18
- package/src/hooks/useEventTheme.ts +5 -1
- package/src/hooks/useFileUrl.ts +52 -8
- package/src/hooks/useOrganisationSecurity.test.ts +2 -1
- package/src/providers/__tests__/ProviderLifecycle.test.tsx +1 -1
- package/src/rbac/__tests__/auth-rbac.e2e.test.tsx +15 -6
- package/src/rbac/__tests__/rbac-functions.test.ts +3 -3
- package/src/rbac/api.test.ts +104 -0
- package/src/rbac/engine.ts +1 -1
- package/src/rbac/hooks/useCan.test.ts +2 -2
- package/src/rbac/secureClient.ts +1 -1
- package/src/rbac/types/functions.ts +1 -1
- package/src/theming/__tests__/parseEventColours.test.ts +117 -8
- package/src/theming/parseEventColours.ts +56 -2
- package/src/types/supabase.ts +2 -3
- package/src/utils/__tests__/bundleAnalysis.unit.test.ts +9 -9
- package/src/utils/file-reference/__tests__/file-reference.test.ts +4 -0
- package/src/utils/formatting/formatDate.test.ts +3 -2
- package/src/utils/formatting/formatDateTime.test.ts +2 -2
- package/src/utils/google-places/googlePlacesUtils.test.ts +36 -24
- package/src/utils/storage/__tests__/helpers.unit.test.ts +19 -12
- package/src/utils/storage/helpers.test.ts +69 -3
|
@@ -80,6 +80,24 @@ function matchesVersionRange(version, range) {
|
|
|
80
80
|
return version.startsWith(range.replace(/[\^~]/, ''));
|
|
81
81
|
}
|
|
82
82
|
|
|
83
|
+
// Compare two version strings (e.g., "4.1.8" vs "4.1.16")
|
|
84
|
+
// Returns: -1 if v1 < v2, 0 if v1 === v2, 1 if v1 > v2
|
|
85
|
+
function compareVersions(v1, v2) {
|
|
86
|
+
const parts1 = v1.split('.').map(Number);
|
|
87
|
+
const parts2 = v2.split('.').map(Number);
|
|
88
|
+
const maxLength = Math.max(parts1.length, parts2.length);
|
|
89
|
+
|
|
90
|
+
for (let i = 0; i < maxLength; i++) {
|
|
91
|
+
const part1 = parts1[i] || 0;
|
|
92
|
+
const part2 = parts2[i] || 0;
|
|
93
|
+
|
|
94
|
+
if (part1 < part2) return -1;
|
|
95
|
+
if (part1 > part2) return 1;
|
|
96
|
+
}
|
|
97
|
+
|
|
98
|
+
return 0;
|
|
99
|
+
}
|
|
100
|
+
|
|
83
101
|
// Check version compatibility
|
|
84
102
|
function checkVersion(installed, required) {
|
|
85
103
|
if (!required) return { valid: true };
|
|
@@ -88,18 +106,46 @@ function checkVersion(installed, required) {
|
|
|
88
106
|
const installedVersion = installed.replace(/[\^~]/, '');
|
|
89
107
|
const requiredVersion = required.replace(/[\^~]/, '');
|
|
90
108
|
|
|
91
|
-
//
|
|
92
|
-
const
|
|
93
|
-
const
|
|
109
|
+
// Parse version parts
|
|
110
|
+
const installedParts = installedVersion.split('.').map(Number);
|
|
111
|
+
const requiredParts = requiredVersion.split('.').map(Number);
|
|
112
|
+
|
|
113
|
+
const installedMajor = installedParts[0] || 0;
|
|
114
|
+
const requiredMajor = requiredParts[0] || 0;
|
|
94
115
|
|
|
95
116
|
if (required.startsWith('^')) {
|
|
117
|
+
// Caret range: ^4.1.16 means >= 4.1.16 and < 5.0.0
|
|
118
|
+
// Check if installed version is >= required version
|
|
119
|
+
const versionComparison = compareVersions(installedVersion, requiredVersion);
|
|
120
|
+
|
|
121
|
+
// Must be same major version and >= required version
|
|
122
|
+
const valid = installedMajor === requiredMajor && versionComparison >= 0;
|
|
123
|
+
|
|
96
124
|
return {
|
|
97
|
-
valid
|
|
125
|
+
valid,
|
|
98
126
|
installed,
|
|
99
127
|
required,
|
|
100
128
|
};
|
|
101
129
|
}
|
|
102
130
|
|
|
131
|
+
if (required.startsWith('~')) {
|
|
132
|
+
// Tilde range: ~4.1.16 means >= 4.1.16 and < 4.2.0
|
|
133
|
+
const versionComparison = compareVersions(installedVersion, requiredVersion);
|
|
134
|
+
const installedMinor = installedParts[1] || 0;
|
|
135
|
+
const requiredMinor = requiredParts[1] || 0;
|
|
136
|
+
|
|
137
|
+
const valid = installedMajor === requiredMajor &&
|
|
138
|
+
installedMinor === requiredMinor &&
|
|
139
|
+
versionComparison >= 0;
|
|
140
|
+
|
|
141
|
+
return {
|
|
142
|
+
valid,
|
|
143
|
+
installed,
|
|
144
|
+
required,
|
|
145
|
+
};
|
|
146
|
+
}
|
|
147
|
+
|
|
148
|
+
// Exact match or no prefix - check major version
|
|
103
149
|
return {
|
|
104
150
|
valid: installedMajor === requiredMajor,
|
|
105
151
|
installed,
|
|
@@ -114,7 +160,7 @@ function runDependencyAudit(consumingAppPath = process.cwd()) {
|
|
|
114
160
|
if (!fs.existsSync(packageJsonPath)) {
|
|
115
161
|
return {
|
|
116
162
|
error: `package.json not found at ${packageJsonPath}`,
|
|
117
|
-
issues: { includedDeps: [], missingRequired: [], missingOptional: [], versionIssues: [], wrongLocation: [] }
|
|
163
|
+
issues: { includedDeps: [], missingRequired: [], missingOptional: [], versionIssues: [], wrongLocation: [], missingDevDeps: [], devVersionIssues: [] }
|
|
118
164
|
};
|
|
119
165
|
}
|
|
120
166
|
|
|
@@ -123,22 +169,41 @@ function runDependencyAudit(consumingAppPath = process.cwd()) {
|
|
|
123
169
|
if (!paceCorePath) {
|
|
124
170
|
return {
|
|
125
171
|
error: 'Could not find pace-core package.json. Make sure @jmruthers/pace-core is installed in your project.',
|
|
126
|
-
issues: { includedDeps: [], missingRequired: [], missingOptional: [], versionIssues: [], wrongLocation: [] }
|
|
172
|
+
issues: { includedDeps: [], missingRequired: [], missingOptional: [], versionIssues: [], wrongLocation: [], missingDevDeps: [], devVersionIssues: [] }
|
|
127
173
|
};
|
|
128
174
|
}
|
|
129
175
|
|
|
130
176
|
const paceCorePkg = JSON.parse(fs.readFileSync(paceCorePath, 'utf8'));
|
|
131
177
|
const INCLUDED_DEPS = Object.keys(paceCorePkg.dependencies || {});
|
|
132
178
|
const PEER_DEPS = paceCorePkg.peerDependencies || {};
|
|
133
|
-
const
|
|
134
|
-
|
|
179
|
+
const PEER_META = paceCorePkg.peerDependenciesMeta || {};
|
|
180
|
+
|
|
181
|
+
// Required peers are those NOT marked as optional in peerDependenciesMeta
|
|
182
|
+
const REQUIRED_PEERS = Object.keys(PEER_DEPS).filter(
|
|
183
|
+
dep => !PEER_META[dep]?.optional
|
|
184
|
+
);
|
|
185
|
+
|
|
186
|
+
// Optional peers are those marked as optional in peerDependenciesMeta
|
|
187
|
+
const OPTIONAL_PEERS = Object.keys(PEER_DEPS).filter(
|
|
188
|
+
dep => PEER_META[dep]?.optional
|
|
189
|
+
);
|
|
190
|
+
|
|
191
|
+
// Validation warnings: check if any peer dependency is missing from peerDependenciesMeta
|
|
192
|
+
const missingMeta = Object.keys(PEER_DEPS).filter(
|
|
193
|
+
dep => !PEER_META[dep]
|
|
194
|
+
);
|
|
195
|
+
|
|
196
|
+
if (missingMeta.length > 0) {
|
|
197
|
+
console.warn(`${colors.yellow}Warning: The following peer dependencies are missing from peerDependenciesMeta: ${missingMeta.join(', ')}${colors.reset}`);
|
|
198
|
+
console.warn(`${colors.yellow}They will be treated as required. Add them to peerDependenciesMeta to mark them as optional.${colors.reset}`);
|
|
199
|
+
}
|
|
135
200
|
|
|
136
201
|
// Verify pace-core is installed
|
|
137
202
|
const paceCoreInNodeModules = path.join(consumingAppPath, 'node_modules', '@jmruthers', 'pace-core', 'package.json');
|
|
138
203
|
if (!fs.existsSync(paceCoreInNodeModules)) {
|
|
139
204
|
return {
|
|
140
205
|
error: '@jmruthers/pace-core not found in node_modules. Please run: npm install @jmruthers/pace-core',
|
|
141
|
-
issues: { includedDeps: [], missingRequired: [], missingOptional: [], versionIssues: [], wrongLocation: [] }
|
|
206
|
+
issues: { includedDeps: [], missingRequired: [], missingOptional: [], versionIssues: [], wrongLocation: [], missingDevDeps: [], devVersionIssues: [] }
|
|
142
207
|
};
|
|
143
208
|
}
|
|
144
209
|
|
|
@@ -154,6 +219,8 @@ function runDependencyAudit(consumingAppPath = process.cwd()) {
|
|
|
154
219
|
missingOptional: [],
|
|
155
220
|
versionIssues: [],
|
|
156
221
|
wrongLocation: [],
|
|
222
|
+
missingDevDeps: [],
|
|
223
|
+
devVersionIssues: [],
|
|
157
224
|
};
|
|
158
225
|
|
|
159
226
|
// Check for included dependencies
|
|
@@ -216,6 +283,107 @@ function runDependencyAudit(consumingAppPath = process.cwd()) {
|
|
|
216
283
|
});
|
|
217
284
|
}
|
|
218
285
|
|
|
286
|
+
// Check required dev dependencies
|
|
287
|
+
// Get pace-core's dev dependencies to use as reference versions
|
|
288
|
+
const paceCoreDevDeps = paceCorePkg.devDependencies || {};
|
|
289
|
+
const devDeps = consumingPkg.devDependencies || {};
|
|
290
|
+
|
|
291
|
+
// Build list of required dev dependencies dynamically from pace-core's devDependencies
|
|
292
|
+
// Exclude packages that consuming apps don't need:
|
|
293
|
+
// 1. pace-core-specific build tools (tsup, typedoc, etc.)
|
|
294
|
+
// 2. Testing libraries (consuming apps may use different testing setups)
|
|
295
|
+
// 3. Type definitions (@types/* - consuming apps manage their own)
|
|
296
|
+
// 4. Packages already checked as peer dependencies (except tailwindcss which we check in both)
|
|
297
|
+
// 5. Packages already included in pace-core's dependencies
|
|
298
|
+
|
|
299
|
+
const requiredDevDeps = {};
|
|
300
|
+
|
|
301
|
+
Object.entries(paceCoreDevDeps).forEach(([dep, version]) => {
|
|
302
|
+
// Skip pace-core-specific build tools
|
|
303
|
+
if (dep === 'tsup' ||
|
|
304
|
+
dep === 'typedoc' ||
|
|
305
|
+
dep.startsWith('typedoc-plugin-') ||
|
|
306
|
+
dep === 'esbuild' ||
|
|
307
|
+
dep === '@vitest/coverage-v8') {
|
|
308
|
+
return;
|
|
309
|
+
}
|
|
310
|
+
|
|
311
|
+
// Skip testing libraries (consuming apps may use different testing)
|
|
312
|
+
if (dep.includes('testing-library') ||
|
|
313
|
+
dep === 'jsdom' ||
|
|
314
|
+
dep === 'vitest') {
|
|
315
|
+
return;
|
|
316
|
+
}
|
|
317
|
+
|
|
318
|
+
// Skip type definitions (consuming apps manage their own @types/*)
|
|
319
|
+
if (dep.startsWith('@types/')) {
|
|
320
|
+
return;
|
|
321
|
+
}
|
|
322
|
+
|
|
323
|
+
// Skip if it's already a peer dependency (checked separately)
|
|
324
|
+
// Exception: tailwindcss - we check both peer and dev dependency versions
|
|
325
|
+
if (PEER_DEPS[dep] && dep !== 'tailwindcss') {
|
|
326
|
+
return;
|
|
327
|
+
}
|
|
328
|
+
|
|
329
|
+
// Skip if it's already in pace-core's dependencies (included, not needed by consuming apps)
|
|
330
|
+
if (INCLUDED_DEPS.includes(dep)) {
|
|
331
|
+
return;
|
|
332
|
+
}
|
|
333
|
+
|
|
334
|
+
// Skip if it's in pace-core's dependencies (shouldn't be in devDependencies)
|
|
335
|
+
if (paceCorePkg.dependencies?.[dep]) {
|
|
336
|
+
return;
|
|
337
|
+
}
|
|
338
|
+
|
|
339
|
+
// Add to required dev dependencies - versions come directly from pace-core's package.json
|
|
340
|
+
requiredDevDeps[dep] = version;
|
|
341
|
+
});
|
|
342
|
+
|
|
343
|
+
// Override tailwindcss version with peer dependency version if available
|
|
344
|
+
// This ensures we use the peer dependency requirement, not the dev dependency version
|
|
345
|
+
if (PEER_DEPS.tailwindcss) {
|
|
346
|
+
requiredDevDeps.tailwindcss = PEER_DEPS.tailwindcss;
|
|
347
|
+
}
|
|
348
|
+
|
|
349
|
+
// Check for missing required dev dependencies
|
|
350
|
+
Object.entries(requiredDevDeps).forEach(([dep, requiredVersion]) => {
|
|
351
|
+
if (!requiredVersion) return; // Skip if pace-core doesn't specify a version
|
|
352
|
+
|
|
353
|
+
if (!devDeps[dep] && !consumingPkg.dependencies?.[dep]) {
|
|
354
|
+
issues.missingDevDeps.push({
|
|
355
|
+
package: dep,
|
|
356
|
+
required: requiredVersion,
|
|
357
|
+
});
|
|
358
|
+
} else {
|
|
359
|
+
// Check version if installed
|
|
360
|
+
const installedVersion = devDeps[dep] || consumingPkg.dependencies?.[dep];
|
|
361
|
+
if (installedVersion) {
|
|
362
|
+
const versionCheck = checkVersion(installedVersion, requiredVersion);
|
|
363
|
+
if (!versionCheck.valid) {
|
|
364
|
+
issues.devVersionIssues.push({
|
|
365
|
+
package: dep,
|
|
366
|
+
installed: versionCheck.installed,
|
|
367
|
+
required: versionCheck.required,
|
|
368
|
+
location: devDeps[dep] ? 'devDependencies' : 'dependencies',
|
|
369
|
+
});
|
|
370
|
+
}
|
|
371
|
+
}
|
|
372
|
+
}
|
|
373
|
+
});
|
|
374
|
+
|
|
375
|
+
// Check for dev dependencies in wrong location (should be in devDependencies, not dependencies)
|
|
376
|
+
// This is dynamic - check all required dev dependencies
|
|
377
|
+
Object.keys(requiredDevDeps).forEach(dep => {
|
|
378
|
+
if (consumingPkg.dependencies?.[dep] && !devDeps[dep]) {
|
|
379
|
+
issues.wrongLocation.push({
|
|
380
|
+
package: dep,
|
|
381
|
+
current: 'dependencies',
|
|
382
|
+
shouldBe: 'devDependencies',
|
|
383
|
+
});
|
|
384
|
+
}
|
|
385
|
+
});
|
|
386
|
+
|
|
219
387
|
// Find pace-core version - check dependencies, devDependencies, or installed package
|
|
220
388
|
let paceCoreVersion = consumingPkg.dependencies?.['@jmruthers/pace-core'] ||
|
|
221
389
|
consumingPkg.devDependencies?.['@jmruthers/pace-core'];
|
|
@@ -315,6 +483,28 @@ function auditDependencies(consumingAppPath = process.cwd()) {
|
|
|
315
483
|
console.log();
|
|
316
484
|
}
|
|
317
485
|
|
|
486
|
+
// Missing dev dependencies (errors)
|
|
487
|
+
if (issues.missingDevDeps.length > 0) {
|
|
488
|
+
hasErrors = true;
|
|
489
|
+
console.log(`${colors.red}❌ MISSING REQUIRED DEV DEPENDENCIES:${colors.reset}`);
|
|
490
|
+
issues.missingDevDeps.forEach(issue => {
|
|
491
|
+
console.log(` - ${colors.red}${issue.package}${colors.reset} (required: ${issue.required})`);
|
|
492
|
+
});
|
|
493
|
+
console.log();
|
|
494
|
+
}
|
|
495
|
+
|
|
496
|
+
// Dev version issues (errors)
|
|
497
|
+
if (issues.devVersionIssues.length > 0) {
|
|
498
|
+
hasErrors = true;
|
|
499
|
+
console.log(`${colors.yellow}⚠️ DEV DEPENDENCY VERSION ISSUES:${colors.reset}`);
|
|
500
|
+
issues.devVersionIssues.forEach(issue => {
|
|
501
|
+
console.log(` - ${colors.yellow}${issue.package}${colors.reset} (in ${issue.location})`);
|
|
502
|
+
console.log(` Installed: ${issue.installed}`);
|
|
503
|
+
console.log(` Required: ${issue.required}`);
|
|
504
|
+
});
|
|
505
|
+
console.log();
|
|
506
|
+
}
|
|
507
|
+
|
|
318
508
|
// Success message
|
|
319
509
|
if (!hasErrors && issues.missingOptional.length === 0 && issues.wrongLocation.length === 0) {
|
|
320
510
|
console.log(`${colors.green}✅ All dependencies are correctly configured!${colors.reset}\n`);
|
|
@@ -339,6 +529,14 @@ function auditDependencies(consumingAppPath = process.cwd()) {
|
|
|
339
529
|
console.log(`npm install ${depsToInstall}\n`);
|
|
340
530
|
}
|
|
341
531
|
|
|
532
|
+
if (issues.missingDevDeps.length > 0) {
|
|
533
|
+
const devDepsToInstall = issues.missingDevDeps
|
|
534
|
+
.map(i => `${i.package}@${i.required}`)
|
|
535
|
+
.join(' ');
|
|
536
|
+
console.log(`${colors.cyan}# Install missing required dev dependencies${colors.reset}`);
|
|
537
|
+
console.log(`npm install -D ${devDepsToInstall}\n`);
|
|
538
|
+
}
|
|
539
|
+
|
|
342
540
|
if (issues.versionIssues.length > 0) {
|
|
343
541
|
const depsToFix = issues.versionIssues
|
|
344
542
|
.map(i => `${i.package}@${i.required}`)
|
|
@@ -347,6 +545,14 @@ function auditDependencies(consumingAppPath = process.cwd()) {
|
|
|
347
545
|
console.log(`npm install ${depsToFix}\n`);
|
|
348
546
|
}
|
|
349
547
|
|
|
548
|
+
if (issues.devVersionIssues.length > 0) {
|
|
549
|
+
const devDepsToFix = issues.devVersionIssues
|
|
550
|
+
.map(i => `${i.package}@${i.required}`)
|
|
551
|
+
.join(' ');
|
|
552
|
+
console.log(`${colors.cyan}# Fix dev dependency version ranges${colors.reset}`);
|
|
553
|
+
console.log(`npm install -D ${devDepsToFix}\n`);
|
|
554
|
+
}
|
|
555
|
+
|
|
350
556
|
if (issues.wrongLocation.length > 0) {
|
|
351
557
|
issues.wrongLocation.forEach(issue => {
|
|
352
558
|
console.log(`${colors.cyan}# Move ${issue.package} to devDependencies${colors.reset}`);
|
|
@@ -197,28 +197,13 @@ function checkTestColocation(consumingAppPath) {
|
|
|
197
197
|
|
|
198
198
|
/**
|
|
199
199
|
* Check Supabase structure
|
|
200
|
+
* Note: Consuming apps don't need migrations directory - only pace-core handles migrations
|
|
200
201
|
*/
|
|
201
202
|
function checkSupabaseStructure(consumingAppPath) {
|
|
202
203
|
const issues = [];
|
|
203
204
|
|
|
204
|
-
|
|
205
|
-
|
|
206
|
-
// Supabase structure is optional
|
|
207
|
-
return issues;
|
|
208
|
-
}
|
|
209
|
-
|
|
210
|
-
// Check for migrations directory
|
|
211
|
-
const migrationsDir = path.join(supabaseDir, 'migrations');
|
|
212
|
-
if (!directoryExists(migrationsDir)) {
|
|
213
|
-
issues.push({
|
|
214
|
-
type: 'supabaseStructure',
|
|
215
|
-
file: 'supabase/migrations/',
|
|
216
|
-
line: 0,
|
|
217
|
-
message: 'supabase/migrations/ directory not found. Database migrations should be stored here.',
|
|
218
|
-
severity: 'warning',
|
|
219
|
-
fix: 'Create supabase/migrations/ directory for database migrations',
|
|
220
|
-
});
|
|
221
|
-
}
|
|
205
|
+
// Consuming apps don't need supabase/migrations - only pace-core handles migrations
|
|
206
|
+
// This check has been removed as it's not applicable to consuming apps
|
|
222
207
|
|
|
223
208
|
return issues;
|
|
224
209
|
}
|
|
@@ -25,8 +25,26 @@ function checkComponentBoundaries(consumingAppPath) {
|
|
|
25
25
|
}
|
|
26
26
|
|
|
27
27
|
const componentFiles = findSourceFiles(srcDir).filter(file => {
|
|
28
|
+
// Only check actual component files, not test files or utility files
|
|
29
|
+
const isTestFile = file.includes('.test.') || file.includes('.spec.');
|
|
30
|
+
const isUtilityFile = file.endsWith('Utils.ts') ||
|
|
31
|
+
file.endsWith('Utils.tsx') ||
|
|
32
|
+
file.endsWith('Helpers.ts') ||
|
|
33
|
+
file.endsWith('Helpers.tsx') ||
|
|
34
|
+
file.includes('testUtils') ||
|
|
35
|
+
file.includes('testHelpers') ||
|
|
36
|
+
file.includes('testAssertions') ||
|
|
37
|
+
file.includes('testSetup');
|
|
38
|
+
|
|
39
|
+
if (isTestFile || isUtilityFile) {
|
|
40
|
+
return false;
|
|
41
|
+
}
|
|
42
|
+
|
|
28
43
|
const dir = path.dirname(file);
|
|
29
|
-
|
|
44
|
+
const isComponentDir = dir.includes('/components/') || dir.includes('/pages/');
|
|
45
|
+
const isComponentFile = file.endsWith('.tsx') || (file.endsWith('.ts') && !isUtilityFile);
|
|
46
|
+
|
|
47
|
+
return isComponentDir && isComponentFile;
|
|
30
48
|
});
|
|
31
49
|
|
|
32
50
|
componentFiles.forEach(filePath => {
|
|
@@ -71,18 +89,28 @@ function checkComponentBoundaries(consumingAppPath) {
|
|
|
71
89
|
});
|
|
72
90
|
|
|
73
91
|
// Check for complex business logic in components
|
|
92
|
+
// Only flag actual business logic patterns, not UI text or comments
|
|
74
93
|
const businessLogicPatterns = [
|
|
75
|
-
/if\s*\([^)]*permission/i,
|
|
76
|
-
/if\s*\([^)]*role/i,
|
|
77
|
-
/calculate
|
|
94
|
+
/if\s*\([^)]*permission[^)]*\)/i, // Permission checks
|
|
95
|
+
/if\s*\([^)]*role[^)]*\)/i, // Role checks
|
|
96
|
+
/calculate\w*\s*\(/i, // Calculation functions
|
|
97
|
+
/compute\w*\s*\(/i, // Computation functions
|
|
98
|
+
/process\w+\s*\(/i, // Process functions (not "Processing..." text)
|
|
78
99
|
];
|
|
79
100
|
|
|
80
101
|
// This is a heuristic - look for complex logic that should be in hooks/utils
|
|
102
|
+
// Exclude common UI text patterns
|
|
103
|
+
const hasUIOnlyText = /Processing\.\.\.|processing\.\.\./i.test(content);
|
|
81
104
|
const hasComplexLogic = businessLogicPatterns.some(pattern => pattern.test(content));
|
|
82
|
-
|
|
105
|
+
|
|
106
|
+
if (hasComplexLogic && !hasUIOnlyText) {
|
|
83
107
|
// Check if logic is in a hook call (acceptable)
|
|
84
108
|
const hasHookCalls = /use[A-Z]\w*\s*\(/.test(content);
|
|
85
|
-
if (
|
|
109
|
+
// Check if it's just a simple conditional render (acceptable)
|
|
110
|
+
const isSimpleConditional = /return\s*\([^)]*\?[^)]*:[^)]*\)/.test(content) ||
|
|
111
|
+
/\{[^}]*\?[^}]*:[^}]*\}/.test(content);
|
|
112
|
+
|
|
113
|
+
if (!hasHookCalls && !isSimpleConditional) {
|
|
86
114
|
issues.push({
|
|
87
115
|
type: 'componentBoundary',
|
|
88
116
|
file: relativePath,
|
|
@@ -377,6 +377,16 @@ function checkPagePermissionGuardCoverage(consumingAppPath) {
|
|
|
377
377
|
}
|
|
378
378
|
|
|
379
379
|
const relativePath = getRelativePath(filePath, consumingAppPath);
|
|
380
|
+
const fileName = path.basename(filePath, path.extname(filePath));
|
|
381
|
+
|
|
382
|
+
// Exclude public/error pages that don't need RBAC protection
|
|
383
|
+
// NotFound, 404, Error, Unauthorized, Forbidden, AccessDenied pages are public
|
|
384
|
+
const isPublicPage = /NotFound|not.*found|404|Error|Unauthorized|Forbidden|AccessDenied/i.test(fileName) ||
|
|
385
|
+
/public|error|unauthorized|forbidden|access.*denied/i.test(relativePath);
|
|
386
|
+
|
|
387
|
+
if (isPublicPage) {
|
|
388
|
+
return; // Skip public/error pages - they don't need PagePermissionGuard
|
|
389
|
+
}
|
|
380
390
|
|
|
381
391
|
// Check if PagePermissionGuard is used
|
|
382
392
|
const hasPagePermissionGuard = /<PagePermissionGuard/.test(content) ||
|
|
@@ -256,7 +256,61 @@ function checkViteConfig(consumingAppPath) {
|
|
|
256
256
|
}
|
|
257
257
|
|
|
258
258
|
// Check for resolve.dedupe (should dedupe React dependencies)
|
|
259
|
-
|
|
259
|
+
// Need to handle nested objects, so look for dedupe anywhere after resolve: {
|
|
260
|
+
// Match resolve: { ... and then look for dedupe before the matching closing brace
|
|
261
|
+
// Use a more flexible pattern that handles nested braces
|
|
262
|
+
const resolvePattern = /resolve\s*:\s*\{/;
|
|
263
|
+
const resolveMatch = content.match(resolvePattern);
|
|
264
|
+
|
|
265
|
+
let hasResolveDedupe = false;
|
|
266
|
+
if (resolveMatch) {
|
|
267
|
+
const resolveStart = resolveMatch.index + resolveMatch[0].length;
|
|
268
|
+
// Find the matching closing brace for the resolve object
|
|
269
|
+
let braceCount = 1;
|
|
270
|
+
let i = resolveStart;
|
|
271
|
+
let resolveEnd = -1;
|
|
272
|
+
|
|
273
|
+
while (i < content.length && braceCount > 0) {
|
|
274
|
+
if (content[i] === '{') braceCount++;
|
|
275
|
+
if (content[i] === '}') braceCount--;
|
|
276
|
+
if (braceCount === 0) {
|
|
277
|
+
resolveEnd = i;
|
|
278
|
+
break;
|
|
279
|
+
}
|
|
280
|
+
i++;
|
|
281
|
+
}
|
|
282
|
+
|
|
283
|
+
if (resolveEnd > 0) {
|
|
284
|
+
const resolveBody = content.substring(resolveStart, resolveEnd);
|
|
285
|
+
// Check if dedupe exists in the resolve object body
|
|
286
|
+
hasResolveDedupe = /dedupe\s*:/.test(resolveBody);
|
|
287
|
+
|
|
288
|
+
// Also verify it includes the required dependencies
|
|
289
|
+
if (hasResolveDedupe) {
|
|
290
|
+
const dedupePattern = /dedupe\s*:\s*\[([^\]]+)\]/;
|
|
291
|
+
const dedupeMatch = resolveBody.match(dedupePattern);
|
|
292
|
+
if (dedupeMatch) {
|
|
293
|
+
const dedupeArray = dedupeMatch[1];
|
|
294
|
+
const hasReact = /['"]react['"]/.test(dedupeArray);
|
|
295
|
+
const hasReactDom = /['"]react-dom['"]/.test(dedupeArray);
|
|
296
|
+
const hasReactRouter = /['"]react-router-dom['"]/.test(dedupeArray);
|
|
297
|
+
|
|
298
|
+
// If any required dependency is missing, flag it
|
|
299
|
+
if (!hasReact || !hasReactDom || !hasReactRouter) {
|
|
300
|
+
issues.push({
|
|
301
|
+
type: 'viteConfig',
|
|
302
|
+
file: relativePath,
|
|
303
|
+
line: 1,
|
|
304
|
+
message: 'vite.config.ts resolve.dedupe is missing required dependencies. Should include: react, react-dom, react-router-dom',
|
|
305
|
+
severity: 'warning',
|
|
306
|
+
fix: 'Update resolve.dedupe to include: [\'react\', \'react-dom\', \'react-router-dom\']',
|
|
307
|
+
});
|
|
308
|
+
}
|
|
309
|
+
}
|
|
310
|
+
}
|
|
311
|
+
}
|
|
312
|
+
}
|
|
313
|
+
|
|
260
314
|
if (!hasResolveDedupe) {
|
|
261
315
|
issues.push({
|
|
262
316
|
type: 'viteConfig',
|
package/audit-tool/index.cjs
CHANGED
|
@@ -164,6 +164,25 @@ function main() {
|
|
|
164
164
|
// Display audit results summary
|
|
165
165
|
console.log(`\n${colors.bold}Audit Results:${colors.reset}\n`);
|
|
166
166
|
|
|
167
|
+
// Display dependency audit first
|
|
168
|
+
if (dependencyResult.error) {
|
|
169
|
+
console.log(` ${colors.red}❌ Dependency Audit: Error - ${dependencyResult.error}${colors.reset}`);
|
|
170
|
+
} else {
|
|
171
|
+
const depIssues = dependencyResult.issues || {};
|
|
172
|
+
const depCount = (depIssues.includedDeps?.length || 0) +
|
|
173
|
+
(depIssues.missingRequired?.length || 0) +
|
|
174
|
+
(depIssues.versionIssues?.length || 0) +
|
|
175
|
+
(depIssues.wrongLocation?.length || 0) +
|
|
176
|
+
(depIssues.missingDevDeps?.length || 0) +
|
|
177
|
+
(depIssues.devVersionIssues?.length || 0);
|
|
178
|
+
|
|
179
|
+
if (depCount === 0) {
|
|
180
|
+
console.log(` ${colors.green}✅ Dependency Audit: 0 issues${colors.reset}`);
|
|
181
|
+
} else {
|
|
182
|
+
console.log(` ${colors.red}❌ Dependency Audit: ${depCount} issue(s)${colors.reset}`);
|
|
183
|
+
}
|
|
184
|
+
}
|
|
185
|
+
|
|
167
186
|
// Display each standard
|
|
168
187
|
Object.entries(standardResults).forEach(([key, result]) => {
|
|
169
188
|
const standardNames = {
|
|
@@ -189,34 +208,19 @@ function main() {
|
|
|
189
208
|
}
|
|
190
209
|
});
|
|
191
210
|
|
|
192
|
-
// Display dependency audit
|
|
193
|
-
if (dependencyResult.error) {
|
|
194
|
-
console.log(` ${colors.red}❌ Dependency Audit: Error - ${dependencyResult.error}${colors.reset}`);
|
|
195
|
-
} else {
|
|
196
|
-
const depIssues = dependencyResult.issues || {};
|
|
197
|
-
const depCount = (depIssues.includedDeps?.length || 0) +
|
|
198
|
-
(depIssues.missingRequired?.length || 0) +
|
|
199
|
-
(depIssues.versionIssues?.length || 0) +
|
|
200
|
-
(depIssues.wrongLocation?.length || 0);
|
|
201
|
-
|
|
202
|
-
if (depCount === 0) {
|
|
203
|
-
console.log(` ${colors.green}✅ Dependency Audit: 0 issues${colors.reset}`);
|
|
204
|
-
} else {
|
|
205
|
-
console.log(` ${colors.red}❌ Dependency Audit: ${depCount} issue(s)${colors.reset}`);
|
|
206
|
-
}
|
|
207
|
-
}
|
|
208
|
-
|
|
209
211
|
// Total summary
|
|
210
212
|
const totalIssues = summary.total + (dependencyResult.error ? 0 :
|
|
211
213
|
((dependencyResult.issues?.includedDeps?.length || 0) +
|
|
212
214
|
(dependencyResult.issues?.missingRequired?.length || 0) +
|
|
213
215
|
(dependencyResult.issues?.versionIssues?.length || 0) +
|
|
214
|
-
(dependencyResult.issues?.wrongLocation?.length || 0)
|
|
216
|
+
(dependencyResult.issues?.wrongLocation?.length || 0) +
|
|
217
|
+
(dependencyResult.issues?.missingDevDeps?.length || 0) +
|
|
218
|
+
(dependencyResult.issues?.devVersionIssues?.length || 0)));
|
|
215
219
|
|
|
216
220
|
console.log(`\n${colors.bold}Total Issues: ${totalIssues === 0 ? colors.green + '0 (All checks passed!)' : colors.red + totalIssues}${colors.reset}\n`);
|
|
217
221
|
|
|
218
222
|
// Generate and save markdown report
|
|
219
|
-
const markdownReport = generateMarkdownReport(standardResults, consumingAppPath);
|
|
223
|
+
const markdownReport = generateMarkdownReport(standardResults, consumingAppPath, dependencyResult);
|
|
220
224
|
|
|
221
225
|
// Generate timestamp in yyyymmddHHMM format
|
|
222
226
|
const now = new Date();
|