--- description: globs: alwaysApply: true --- # Pre-Commit Quality Checks ```yaml name: Pre-Commit Quality Checks description: Runs quality checks similar to GitHub Actions locally before commits author: Cursor AI version: 1.0.0 tags: - quality - pre-commit - testing - linting activation: always: true events: - pre_commit - command triggers: - pre_commit - command: "run_quality_checks" ``` ## Rule Definition This rule runs the same quality checks locally that would normally run in CI, preventing commits that would fail in the GitHub workflow quality-checks.yml. ## Implementation ```javascript const { execSync } = require('child_process'); const fs = require('fs'); const path = require('path'); /** * Determines which checks to run based on changed files * @param {string[]} changedFiles - List of changed files * @returns {Object} Object indicating which checks to run */ function determineChecksToRun(changedFiles) { const checks = { serverChecks: false, clientChecks: false, }; // Check if server files have changed checks.serverChecks = changedFiles.some(file => file.startsWith('app/server/') ); // Check if client files have changed checks.clientChecks = changedFiles.some(file => file.startsWith('app/client/') ); return checks; } /** * Gets a list of changed files in the current git staging area * @returns {string[]} List of changed files */ function getChangedFiles() { try { const output = execSync('git diff --cached --name-only', { encoding: 'utf8' }); return output.split('\n').filter(Boolean); } catch (error) { console.error('Error getting changed files:', error.message); return []; } } /** * Runs client-side quality checks * @returns {Object} Results of the checks */ function runClientChecks() { const results = { success: true, errors: [], output: [] }; try { // Run client lint console.log('Running client lint checks...'); try { const lintOutput = execSync('cd app/client && yarn lint', { encoding: 'utf8' }); results.output.push('✅ Client lint passed'); } catch (error) { results.success = false; results.errors.push('Client lint failed'); results.output.push(`❌ Client lint failed: ${error.message}`); } // Run client unit tests console.log('Running client unit tests...'); try { const testOutput = execSync('cd app/client && yarn test', { encoding: 'utf8' }); results.output.push('✅ Client unit tests passed'); } catch (error) { results.success = false; results.errors.push('Client unit tests failed'); results.output.push(`❌ Client unit tests failed: ${error.message}`); } // Check for cyclic dependencies console.log('Checking for cyclic dependencies...'); try { const cyclicCheckOutput = execSync('cd app/client && yarn check-circular-deps', { encoding: 'utf8' }); results.output.push('✅ No cyclic dependencies found'); } catch (error) { results.success = false; results.errors.push('Cyclic dependencies check failed'); results.output.push(`❌ Cyclic dependencies found: ${error.message}`); } // Run prettier check console.log('Running prettier check...'); try { const prettierOutput = execSync('cd app/client && yarn prettier', { encoding: 'utf8' }); results.output.push('✅ Prettier check passed'); } catch (error) { results.success = false; results.errors.push('Prettier check failed'); results.output.push(`❌ Prettier check failed: ${error.message}`); } } catch (error) { results.success = false; results.errors.push(`General error in client checks: ${error.message}`); } return results; } /** * Runs server-side quality checks * @returns {Object} Results of the checks */ function runServerChecks() { const results = { success: true, errors: [], output: [] }; try { // Run server unit tests console.log('Running server unit tests...'); try { const testOutput = execSync('cd app/server && ./gradlew test', { encoding: 'utf8' }); results.output.push('✅ Server unit tests passed'); } catch (error) { results.success = false; results.errors.push('Server unit tests failed'); results.output.push(`❌ Server unit tests failed: ${error.message}`); } // Run server spotless check console.log('Running server spotless check...'); try { const spotlessOutput = execSync('cd app/server && ./gradlew spotlessCheck', { encoding: 'utf8' }); results.output.push('✅ Server spotless check passed'); } catch (error) { results.success = false; results.errors.push('Server spotless check failed'); results.output.push(`❌ Server spotless check failed: ${error.message}`); } } catch (error) { results.success = false; results.errors.push(`General error in server checks: ${error.message}`); } return results; } /** * Runs all quality checks * @param {Object} context - The execution context * @returns {Object} Results of the checks */ function runQualityChecks(context) { console.log('Running pre-commit quality checks...'); const changedFiles = getChangedFiles(); if (!changedFiles.length) { return { status: 'success', message: 'No files to check' }; } const checksToRun = determineChecksToRun(changedFiles); const results = { success: true, output: ['Starting quality checks for staged files...'], clientChecks: null, serverChecks: null }; // Run client checks if client files have changed if (checksToRun.clientChecks) { results.output.push('\n=== Client Checks ==='); results.clientChecks = runClientChecks(); results.output = results.output.concat(results.clientChecks.output); results.success = results.success && results.clientChecks.success; } // Run server checks if server files have changed if (checksToRun.serverChecks) { results.output.push('\n=== Server Checks ==='); results.serverChecks = runServerChecks(); results.output = results.output.concat(results.serverChecks.output); results.success = results.success && results.serverChecks.success; } // If no checks were run, note that if (!checksToRun.clientChecks && !checksToRun.serverChecks) { results.output.push('No client or server files were changed, skipping checks'); } if (results.success) { return { status: 'success', message: 'All quality checks passed', details: results.output.join('\n') }; } else { return { status: 'failure', message: 'Quality checks failed', details: results.output.join('\n') }; } } /** * Register command and hooks * @param {Object} context - The cursor context */ function activate(context) { // Register pre-commit hook context.on('pre_commit', (event) => { return runQualityChecks(context); }); // Register command for manual validation context.registerCommand('run_quality_checks', () => { return runQualityChecks(context); }); } module.exports = { activate, runQualityChecks }; ``` ## Usage This rule runs automatically on pre-commit events. You can also manually trigger it with the command `run_quality_checks`. ### What It Checks 1. **For Client-side Changes:** - Runs linting checks - Runs unit tests - Checks for cyclic dependencies - Runs prettier formatting validation 2. **For Server-side Changes:** - Runs unit tests - Runs spotless formatting checks ### Behavior - Only runs checks relevant to the files being committed (client and/or server) - Prevents commits if any checks fail - Provides detailed output about which checks passed or failed ### Requirements - Node.js and yarn for client-side checks - Java and Gradle for server-side checks - Git for determining changed files ### Customization You can customize which checks are run by modifying the `runClientChecks` and `runServerChecks` functions. ### Example Output ``` Running pre-commit quality checks... Starting quality checks for staged files... === Client Checks === ✅ Client lint passed ✅ Client unit tests passed ✅ No cyclic dependencies found ✅ Prettier check passed === Server Checks === ✅ Server unit tests passed ✅ Server spotless check passed ```