Files
everything-claude-code/scripts/hooks/plugin-hook-bootstrap.js
2026-04-14 20:24:21 -07:00

154 lines
3.6 KiB
JavaScript

#!/usr/bin/env node
'use strict';
const fs = require('fs');
const path = require('path');
const { spawnSync } = require('child_process');
function readStdinRaw() {
try {
return fs.readFileSync(0, 'utf8');
} catch (_error) {
return '';
}
}
function writeStderr(stderr) {
if (typeof stderr === 'string' && stderr.length > 0) {
process.stderr.write(stderr);
}
}
function passthrough(raw, result) {
const stdout = typeof result?.stdout === 'string' ? result.stdout : '';
if (stdout) {
process.stdout.write(stdout);
return;
}
if (!Number.isInteger(result?.status) || result.status === 0) {
process.stdout.write(raw);
}
}
function resolveTarget(rootDir, relPath) {
const resolvedRoot = path.resolve(rootDir);
const resolvedTarget = path.resolve(rootDir, relPath);
if (
resolvedTarget !== resolvedRoot &&
!resolvedTarget.startsWith(resolvedRoot + path.sep)
) {
throw new Error(`Path traversal rejected: ${relPath}`);
}
return resolvedTarget;
}
function findShellBinary() {
const candidates = [];
if (process.env.BASH && process.env.BASH.trim()) {
candidates.push(process.env.BASH.trim());
}
if (process.platform === 'win32') {
candidates.push('bash.exe', 'bash');
} else {
candidates.push('bash', 'sh');
}
for (const candidate of candidates) {
const probe = spawnSync(candidate, ['-c', ':'], {
stdio: 'ignore',
windowsHide: true,
});
if (!probe.error) {
return candidate;
}
}
return null;
}
function spawnNode(rootDir, relPath, raw, args) {
return spawnSync(process.execPath, [resolveTarget(rootDir, relPath), ...args], {
input: raw,
encoding: 'utf8',
env: {
...process.env,
CLAUDE_PLUGIN_ROOT: rootDir,
ECC_PLUGIN_ROOT: rootDir,
},
cwd: process.cwd(),
timeout: 30000,
windowsHide: true,
});
}
function spawnShell(rootDir, relPath, raw, args) {
const shell = findShellBinary();
if (!shell) {
return {
status: 0,
stdout: '',
stderr: '[Hook] shell runtime unavailable; skipping shell-backed hook\n',
};
}
return spawnSync(shell, [resolveTarget(rootDir, relPath), ...args], {
input: raw,
encoding: 'utf8',
env: {
...process.env,
CLAUDE_PLUGIN_ROOT: rootDir,
ECC_PLUGIN_ROOT: rootDir,
},
cwd: process.cwd(),
timeout: 30000,
windowsHide: true,
});
}
function main() {
const [, , mode, relPath, ...args] = process.argv;
const raw = readStdinRaw();
const rootDir = process.env.CLAUDE_PLUGIN_ROOT || process.env.ECC_PLUGIN_ROOT;
if (!mode || !relPath || !rootDir) {
process.stdout.write(raw);
process.exit(0);
}
let result;
try {
if (mode === 'node') {
result = spawnNode(rootDir, relPath, raw, args);
} else if (mode === 'shell') {
result = spawnShell(rootDir, relPath, raw, args);
} else {
writeStderr(`[Hook] unknown bootstrap mode: ${mode}\n`);
process.stdout.write(raw);
process.exit(0);
}
} catch (error) {
writeStderr(`[Hook] bootstrap resolution failed: ${error.message}\n`);
process.stdout.write(raw);
process.exit(0);
}
passthrough(raw, result);
writeStderr(result.stderr);
if (result.error || result.signal || result.status === null) {
const reason = result.error
? result.error.message
: result.signal
? `terminated by signal ${result.signal}`
: 'missing exit status';
writeStderr(`[Hook] bootstrap execution failed: ${reason}\n`);
process.exit(0);
}
process.exit(Number.isInteger(result.status) ? result.status : 0);
}
main();