Initial commit
This commit is contained in:
16
hooks/hooks.json
Normal file
16
hooks/hooks.json
Normal file
@@ -0,0 +1,16 @@
|
||||
{
|
||||
"hooks": {
|
||||
"PostToolUse": [
|
||||
{
|
||||
"matcher": "Write|Edit",
|
||||
"hooks": [
|
||||
{
|
||||
"type": "command",
|
||||
"command": "node ${CLAUDE_PLUGIN_ROOT}/hooks/scripts/eslint-fix.cjs",
|
||||
"timeout": 30
|
||||
}
|
||||
]
|
||||
}
|
||||
]
|
||||
}
|
||||
}
|
||||
136
hooks/scripts/eslint-fix.cjs
Executable file
136
hooks/scripts/eslint-fix.cjs
Executable file
@@ -0,0 +1,136 @@
|
||||
#!/usr/bin/env node
|
||||
const { execSync } = require('child_process');
|
||||
const { readFileSync } = require('fs');
|
||||
const { basename, relative } = require('path');
|
||||
|
||||
/**
|
||||
* @typedef {Object} HookInput
|
||||
* @property {string} [cwd]
|
||||
* @property {string} [file_path]
|
||||
*/
|
||||
|
||||
/**
|
||||
* @typedef {Object} ESLintMessage
|
||||
* @property {number} line
|
||||
* @property {number} column
|
||||
* @property {string} message
|
||||
* @property {number} severity
|
||||
* @property {string} [ruleId]
|
||||
*/
|
||||
|
||||
/**
|
||||
* @typedef {Object} ESLintResult
|
||||
* @property {ESLintMessage[]} [messages]
|
||||
*/
|
||||
|
||||
// Read hook input from stdin
|
||||
/** @type {HookInput} */
|
||||
let input;
|
||||
try {
|
||||
input = JSON.parse(readFileSync(0, 'utf-8'));
|
||||
} catch {
|
||||
// Invalid JSON, skip gracefully
|
||||
console.log(JSON.stringify({ continue: true }));
|
||||
process.exit(0);
|
||||
}
|
||||
|
||||
const { cwd, file_path } = input;
|
||||
|
||||
// Validate required file_path field
|
||||
if (!file_path) {
|
||||
console.log(JSON.stringify({ continue: true }));
|
||||
process.exit(0);
|
||||
}
|
||||
|
||||
// Only process JS/TS files (including .d.ts declaration files)
|
||||
const JS_TS_EXTENSIONS = /\.(ts|tsx|js|jsx|cjs|mjs|mts|cts)$/i;
|
||||
if (!JS_TS_EXTENSIONS.test(file_path)) {
|
||||
console.log(JSON.stringify({ continue: true }));
|
||||
process.exit(0);
|
||||
}
|
||||
|
||||
// Check if eslint is available
|
||||
try {
|
||||
execSync('npx eslint --version', {
|
||||
cwd,
|
||||
stdio: 'ignore',
|
||||
});
|
||||
} catch {
|
||||
// ESLint not available, skip silently
|
||||
console.log(JSON.stringify({ continue: true }));
|
||||
process.exit(0);
|
||||
}
|
||||
|
||||
// Run eslint --fix with JSON output
|
||||
// Use relative path to avoid ESLint base path issues
|
||||
const relativePath = cwd ? relative(cwd, file_path) : file_path;
|
||||
/** @type {string} */
|
||||
let output;
|
||||
try {
|
||||
output = execSync(`npx eslint --fix --format json "${relativePath}"`, {
|
||||
cwd,
|
||||
encoding: 'utf-8',
|
||||
stdio: 'pipe',
|
||||
});
|
||||
} catch (error) {
|
||||
// ESLint exits with non-zero when errors exist
|
||||
const stdout =
|
||||
error && typeof error === 'object' && 'stdout' in error
|
||||
? error.stdout
|
||||
: null;
|
||||
output = (typeof stdout === 'string' ? stdout : null) || '[]';
|
||||
}
|
||||
|
||||
// Parse results and extract remaining errors
|
||||
/** @type {ESLintResult[]} */
|
||||
let results;
|
||||
try {
|
||||
const parsed = JSON.parse(output);
|
||||
results = Array.isArray(parsed) ? parsed : [];
|
||||
} catch {
|
||||
// Invalid JSON, assume no errors
|
||||
results = [];
|
||||
}
|
||||
|
||||
const errors =
|
||||
results[0]?.messages?.filter(
|
||||
(/** @type {ESLintMessage} */ msg) => msg.severity === 2,
|
||||
) || [];
|
||||
|
||||
// Format and report errors if any exist
|
||||
if (errors.length > 0) {
|
||||
const fileName = basename(file_path);
|
||||
const MAX_ERRORS_TO_SHOW = 5;
|
||||
|
||||
let errorList;
|
||||
if (errors.length > MAX_ERRORS_TO_SHOW) {
|
||||
const shown = errors.slice(0, MAX_ERRORS_TO_SHOW);
|
||||
errorList = shown
|
||||
.map(
|
||||
(/** @type {ESLintMessage} */ e) =>
|
||||
`Line ${e.line}:${e.column}: ${e.message} (${e.ruleId || 'unknown'})`,
|
||||
)
|
||||
.join('\n');
|
||||
errorList += `\n...and ${errors.length - MAX_ERRORS_TO_SHOW} more error(s)`;
|
||||
} else {
|
||||
errorList = errors
|
||||
.map(
|
||||
(/** @type {ESLintMessage} */ e) =>
|
||||
`Line ${e.line}:${e.column}: ${e.message} (${e.ruleId || 'unknown'})`,
|
||||
)
|
||||
.join('\n');
|
||||
}
|
||||
|
||||
const message = `ESLint found ${errors.length} error(s) in ${fileName}:\n${errorList}`;
|
||||
|
||||
console.log(
|
||||
JSON.stringify({
|
||||
continue: true,
|
||||
systemMessage: message,
|
||||
}),
|
||||
);
|
||||
} else {
|
||||
console.log(JSON.stringify({ continue: true }));
|
||||
}
|
||||
|
||||
process.exit(0);
|
||||
Reference in New Issue
Block a user