Files
main_frontend/.codex/hooks/format_ts_changes.mjs
T
Daniil 21e936a827
dev / deploy (push) Successful in 2m15s
compute / deploy (push) Has been cancelled
chore: agentic upgrade
2026-05-17 02:11:33 +03:00

111 lines
2.9 KiB
JavaScript

import { existsSync, readFileSync, rmSync, statSync } from 'node:fs';
import { join } from 'node:path';
import { spawnSync } from 'node:child_process';
import { createHash } from 'node:crypto';
import { tmpdir } from 'node:os';
const readInput = () => {
try {
const rawInput = readFileSync(0, 'utf8').trim();
return rawInput ? JSON.parse(rawInput) : {};
} catch {
return {};
}
};
const runGit = (args) => {
const result = spawnSync('git', args, { encoding: 'utf8' });
return result.status === 0 ? result.stdout.split('\n').filter(Boolean) : [];
};
const runCommand = (command, args) =>
spawnSync(command, args, {
encoding: 'utf8',
shell: false,
});
const getStatePath = (input) => {
const turnId = String(input.turn_id ?? 'unknown').replace(/[^a-zA-Z0-9_.-]/g, '_');
const cwdHash = createHash('sha256').update(process.cwd()).digest('hex').slice(0, 16);
return join(tmpdir(), 'codex-fastboard-hooks', cwdHash, `${turnId}.json`);
};
const isTypeScriptSource = (file) => /^src\/.+\.tsx?$/.test(file);
const getDirtyTypeScriptFiles = () => {
const files = [
...runGit(['diff', '--name-only', '--diff-filter=ACMR']),
...runGit(['diff', '--cached', '--name-only', '--diff-filter=ACMR']),
...runGit(['ls-files', '--others', '--exclude-standard']),
];
return [...new Set(files)].filter(isTypeScriptSource).sort();
};
const getFileState = (file) => {
try {
const stat = statSync(file);
return { mtimeMs: stat.mtimeMs, size: stat.size };
} catch {
return null;
}
};
const hasChangedSinceSnapshot = (beforeState, file) => {
const before = beforeState[file];
const current = getFileState(file);
if (!current) {
return false;
}
return !before || before.mtimeMs !== current.mtimeMs || before.size !== current.size;
};
const firstLines = (value, maxLines = 30) =>
value
.split('\n')
.filter(Boolean)
.slice(0, maxLines)
.join('\n');
const report = (message) => {
process.stdout.write(
JSON.stringify({
continue: true,
systemMessage: message,
}),
);
};
const input = readInput();
const statePath = getStatePath(input);
const beforeState = existsSync(statePath) ? JSON.parse(readFileSync(statePath, 'utf8')) : {};
const files = getDirtyTypeScriptFiles().filter((file) => hasChangedSinceSnapshot(beforeState, file));
if (existsSync(statePath)) {
rmSync(statePath, { force: true });
}
if (files.length === 0) {
process.exit(0);
}
const prettier = runCommand('npx', ['prettier', '--write', ...files]);
if (prettier.status !== 0) {
report(`Prettier hook failed:\n${firstLines(`${prettier.stdout}\n${prettier.stderr}`)}`);
process.exit(0);
}
const eslint = runCommand('npx', [
'eslint',
'--no-error-on-unmatched-pattern',
'--max-warnings=0',
...files,
]);
if (eslint.status !== 0) {
report(`ESLint hook found issues:\n${firstLines(`${eslint.stdout}\n${eslint.stderr}`)}`);
}