mirror of
https://github.com/affaan-m/everything-claude-code.git
synced 2026-03-31 06:03:29 +08:00
Invoke hook scripts directly via require() when they export a run(rawInput) function, eliminating one Node.js process spawn per hook invocation (~50-100ms). Includes path traversal guard, timeouts, error logging, PR review feedback, legacy hooks guard, normalized filePath, and restored findProjectRoot config detection with package manager support.
247 lines
8.3 KiB
JavaScript
247 lines
8.3 KiB
JavaScript
/**
|
|
* Tests for scripts/lib/resolve-formatter.js
|
|
*
|
|
* Run with: node tests/lib/resolve-formatter.test.js
|
|
*/
|
|
|
|
const assert = require('assert');
|
|
const path = require('path');
|
|
const fs = require('fs');
|
|
const os = require('os');
|
|
|
|
const { findProjectRoot, detectFormatter, resolveFormatterBin, clearCaches } = require('../../scripts/lib/resolve-formatter');
|
|
|
|
/**
|
|
* Run a single test case, printing pass/fail.
|
|
*
|
|
* @param {string} name - Test description
|
|
* @param {() => void} fn - Test body (throws on failure)
|
|
* @returns {boolean} Whether the test passed
|
|
*/
|
|
function test(name, fn) {
|
|
try {
|
|
fn();
|
|
console.log(` ✓ ${name}`);
|
|
return true;
|
|
} catch (err) {
|
|
console.log(` ✗ ${name}`);
|
|
console.log(` Error: ${err.message}`);
|
|
return false;
|
|
}
|
|
}
|
|
|
|
/** Track all created tmp dirs for cleanup */
|
|
const tmpDirs = [];
|
|
|
|
/**
|
|
* Create a temporary directory and track it for cleanup.
|
|
*
|
|
* @returns {string} Absolute path to the new temp directory
|
|
*/
|
|
function makeTmpDir() {
|
|
const dir = fs.mkdtempSync(path.join(os.tmpdir(), 'resolve-fmt-'));
|
|
tmpDirs.push(dir);
|
|
return dir;
|
|
}
|
|
|
|
/**
|
|
* Remove all tracked temporary directories.
|
|
*/
|
|
function cleanupTmpDirs() {
|
|
for (const dir of tmpDirs) {
|
|
try {
|
|
fs.rmSync(dir, { recursive: true, force: true });
|
|
} catch {
|
|
// Best-effort cleanup
|
|
}
|
|
}
|
|
tmpDirs.length = 0;
|
|
}
|
|
|
|
function runTests() {
|
|
console.log('\n=== Testing resolve-formatter.js ===\n');
|
|
|
|
let passed = 0;
|
|
let failed = 0;
|
|
|
|
function run(name, fn) {
|
|
clearCaches();
|
|
if (test(name, fn)) passed++;
|
|
else failed++;
|
|
}
|
|
|
|
// ── findProjectRoot ───────────────────────────────────────────
|
|
|
|
run('findProjectRoot: finds package.json in parent dir', () => {
|
|
const root = makeTmpDir();
|
|
const sub = path.join(root, 'src', 'lib');
|
|
fs.mkdirSync(sub, { recursive: true });
|
|
fs.writeFileSync(path.join(root, 'package.json'), '{}');
|
|
|
|
assert.strictEqual(findProjectRoot(sub), root);
|
|
});
|
|
|
|
run('findProjectRoot: returns startDir when no package.json', () => {
|
|
const root = makeTmpDir();
|
|
const sub = path.join(root, 'deep');
|
|
fs.mkdirSync(sub, { recursive: true });
|
|
|
|
// No package.json anywhere in tmp → falls back to startDir
|
|
assert.strictEqual(findProjectRoot(sub), sub);
|
|
});
|
|
|
|
run('findProjectRoot: caches result for same startDir', () => {
|
|
const root = makeTmpDir();
|
|
fs.writeFileSync(path.join(root, 'package.json'), '{}');
|
|
|
|
const first = findProjectRoot(root);
|
|
// Remove package.json — cache should still return the old result
|
|
fs.unlinkSync(path.join(root, 'package.json'));
|
|
const second = findProjectRoot(root);
|
|
|
|
assert.strictEqual(first, second);
|
|
});
|
|
|
|
// ── detectFormatter ───────────────────────────────────────────
|
|
|
|
run('detectFormatter: detects biome.json', () => {
|
|
const root = makeTmpDir();
|
|
fs.writeFileSync(path.join(root, 'biome.json'), '{}');
|
|
assert.strictEqual(detectFormatter(root), 'biome');
|
|
});
|
|
|
|
run('detectFormatter: detects biome.jsonc', () => {
|
|
const root = makeTmpDir();
|
|
fs.writeFileSync(path.join(root, 'biome.jsonc'), '{}');
|
|
assert.strictEqual(detectFormatter(root), 'biome');
|
|
});
|
|
|
|
run('detectFormatter: detects .prettierrc', () => {
|
|
const root = makeTmpDir();
|
|
fs.writeFileSync(path.join(root, '.prettierrc'), '{}');
|
|
assert.strictEqual(detectFormatter(root), 'prettier');
|
|
});
|
|
|
|
run('detectFormatter: detects prettier.config.js', () => {
|
|
const root = makeTmpDir();
|
|
fs.writeFileSync(path.join(root, 'prettier.config.js'), 'module.exports = {}');
|
|
assert.strictEqual(detectFormatter(root), 'prettier');
|
|
});
|
|
|
|
run('detectFormatter: detects prettier key in package.json', () => {
|
|
const root = makeTmpDir();
|
|
fs.writeFileSync(path.join(root, 'package.json'), JSON.stringify({ name: 'test', prettier: { singleQuote: true } }));
|
|
assert.strictEqual(detectFormatter(root), 'prettier');
|
|
});
|
|
|
|
run('detectFormatter: ignores package.json without prettier key', () => {
|
|
const root = makeTmpDir();
|
|
fs.writeFileSync(path.join(root, 'package.json'), JSON.stringify({ name: 'test' }));
|
|
assert.strictEqual(detectFormatter(root), null);
|
|
});
|
|
|
|
run('detectFormatter: biome takes priority over prettier', () => {
|
|
const root = makeTmpDir();
|
|
fs.writeFileSync(path.join(root, 'biome.json'), '{}');
|
|
fs.writeFileSync(path.join(root, '.prettierrc'), '{}');
|
|
assert.strictEqual(detectFormatter(root), 'biome');
|
|
});
|
|
|
|
run('detectFormatter: returns null when no config found', () => {
|
|
const root = makeTmpDir();
|
|
assert.strictEqual(detectFormatter(root), null);
|
|
});
|
|
|
|
// ── resolveFormatterBin ───────────────────────────────────────
|
|
|
|
run('resolveFormatterBin: uses local biome binary when available', () => {
|
|
const root = makeTmpDir();
|
|
const binDir = path.join(root, 'node_modules', '.bin');
|
|
fs.mkdirSync(binDir, { recursive: true });
|
|
const binName = process.platform === 'win32' ? 'biome.cmd' : 'biome';
|
|
fs.writeFileSync(path.join(binDir, binName), '');
|
|
|
|
const result = resolveFormatterBin(root, 'biome');
|
|
assert.strictEqual(result.bin, path.join(binDir, binName));
|
|
assert.deepStrictEqual(result.prefix, []);
|
|
});
|
|
|
|
run('resolveFormatterBin: falls back to npx for biome', () => {
|
|
const root = makeTmpDir();
|
|
const result = resolveFormatterBin(root, 'biome');
|
|
const expectedBin = process.platform === 'win32' ? 'npx.cmd' : 'npx';
|
|
assert.strictEqual(result.bin, expectedBin);
|
|
assert.deepStrictEqual(result.prefix, ['@biomejs/biome']);
|
|
});
|
|
|
|
run('resolveFormatterBin: uses local prettier binary when available', () => {
|
|
const root = makeTmpDir();
|
|
const binDir = path.join(root, 'node_modules', '.bin');
|
|
fs.mkdirSync(binDir, { recursive: true });
|
|
const binName = process.platform === 'win32' ? 'prettier.cmd' : 'prettier';
|
|
fs.writeFileSync(path.join(binDir, binName), '');
|
|
|
|
const result = resolveFormatterBin(root, 'prettier');
|
|
assert.strictEqual(result.bin, path.join(binDir, binName));
|
|
assert.deepStrictEqual(result.prefix, []);
|
|
});
|
|
|
|
run('resolveFormatterBin: falls back to npx for prettier', () => {
|
|
const root = makeTmpDir();
|
|
const result = resolveFormatterBin(root, 'prettier');
|
|
const expectedBin = process.platform === 'win32' ? 'npx.cmd' : 'npx';
|
|
assert.strictEqual(result.bin, expectedBin);
|
|
assert.deepStrictEqual(result.prefix, ['prettier']);
|
|
});
|
|
|
|
run('resolveFormatterBin: returns null for unknown formatter', () => {
|
|
const root = makeTmpDir();
|
|
const result = resolveFormatterBin(root, 'unknown');
|
|
assert.strictEqual(result, null);
|
|
});
|
|
|
|
run('resolveFormatterBin: caches resolved binary', () => {
|
|
const root = makeTmpDir();
|
|
const binDir = path.join(root, 'node_modules', '.bin');
|
|
fs.mkdirSync(binDir, { recursive: true });
|
|
const binName = process.platform === 'win32' ? 'biome.cmd' : 'biome';
|
|
fs.writeFileSync(path.join(binDir, binName), '');
|
|
|
|
const first = resolveFormatterBin(root, 'biome');
|
|
fs.unlinkSync(path.join(binDir, binName));
|
|
const second = resolveFormatterBin(root, 'biome');
|
|
|
|
assert.strictEqual(first.bin, second.bin);
|
|
});
|
|
|
|
// ── clearCaches ───────────────────────────────────────────────
|
|
|
|
run('clearCaches: clears all cached values', () => {
|
|
const root = makeTmpDir();
|
|
fs.writeFileSync(path.join(root, 'package.json'), '{}');
|
|
fs.writeFileSync(path.join(root, 'biome.json'), '{}');
|
|
|
|
findProjectRoot(root);
|
|
detectFormatter(root);
|
|
resolveFormatterBin(root, 'biome');
|
|
|
|
clearCaches();
|
|
|
|
// After clearing, removing config should change detection
|
|
fs.unlinkSync(path.join(root, 'biome.json'));
|
|
assert.strictEqual(detectFormatter(root), null);
|
|
});
|
|
|
|
// ── Summary & Cleanup ─────────────────────────────────────────
|
|
|
|
cleanupTmpDirs();
|
|
|
|
console.log('\n=== Test Results ===');
|
|
console.log(`Passed: ${passed}`);
|
|
console.log(`Failed: ${failed}`);
|
|
console.log(`Total: ${passed + failed}`);
|
|
process.exit(failed > 0 ? 1 : 0);
|
|
}
|
|
|
|
runTests();
|