Files
gh-boneskull-claude-plugins…/hooks/scripts/eslint-fix.cjs
2025-11-29 18:01:42 +08:00

137 lines
3.3 KiB
JavaScript
Executable File

#!/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);