ktg-plugin-marketplace/plugins/config-audit/hooks/scripts/auto-backup-config.mjs

84 lines
2 KiB
JavaScript

#!/usr/bin/env node
/**
* PreToolUse hook: auto-backup config files before Edit/Write.
* Reads $TOOL_INPUT to check if the target file is a config file.
* If yes, backs it up via scanners/lib/backup.mjs.
* Fast path — no scanner execution.
*/
import { existsSync } from 'node:fs';
import { basename, dirname, sep } from 'node:path';
// Config file patterns to protect
const CONFIG_PATTERNS = [
/CLAUDE\.md$/i,
/CLAUDE\.local\.md$/i,
/settings\.json$/,
/settings\.local\.json$/,
/hooks\.json$/,
/\.mcp\.json$/,
/keybindings\.json$/,
];
const CONFIG_DIRS = ['rules'];
function isConfigFile(filePath) {
if (!filePath) return false;
const name = basename(filePath);
const dir = dirname(filePath);
// Check filename patterns
for (const pattern of CONFIG_PATTERNS) {
if (pattern.test(name)) return true;
}
// Check if inside a rules/ directory
for (const d of CONFIG_DIRS) {
if (dir.includes(`${sep}${d}${sep}`) || dir.endsWith(`${sep}${d}`)) {
if (name.endsWith('.md')) return true;
}
}
return false;
}
/**
* Read all data from stdin asynchronously.
* @returns {Promise<string>}
*/
function readStdin() {
return new Promise((resolve, reject) => {
const chunks = [];
process.stdin.setEncoding('utf-8');
process.stdin.on('data', chunk => chunks.push(chunk));
process.stdin.on('end', () => resolve(chunks.join('')));
process.stdin.on('error', reject);
});
}
async function main() {
let input;
try {
input = await readStdin();
} catch {
process.exit(0);
}
let toolInput;
try {
toolInput = JSON.parse(input);
} catch {
process.exit(0);
}
const filePath = toolInput.file_path || toolInput.path;
if (!filePath || !isConfigFile(filePath) || !existsSync(filePath)) {
process.exit(0);
}
const { createBackup } = await import('../../scanners/lib/backup.mjs');
const { backupPath } = createBackup([filePath]);
process.stderr.write(`[config-audit] Auto-backup: ${basename(filePath)}${backupPath}\n`);
}
main().catch(() => process.exit(0));