Files
2026-07-31 13:12:54 -04:00

71 lines
2.9 KiB
JavaScript

import fs from 'node:fs';
import path from 'node:path';
import { z } from 'zod';
import { camelCase, pascalCase } from '../../lib/strings.js';
import { resolveWorkspacePath } from '../../lib/sandbox.js';
import { errorResult, jsonResult, toErrorMessage } from '../shared.js';
import { requireReact } from './requireReact.js';
export function registerGenerateReactHookTool(server) {
server.registerTool('generate_react_hook', {
title: 'Generate React Hook',
description: 'Scaffold a custom React hook (useXyz.ts) and a matching test file. Detects TypeScript from tsconfig.json.',
inputSchema: {
name: z
.string()
.min(1)
.describe('Hook name without the "use" prefix, e.g. "Counter" or "fetchUser" (camelCase is fine).'),
directory: z
.string()
.optional()
.describe('Directory relative to the React project root. Defaults to "src/hooks".'),
withTest: z.boolean().optional().default(true),
overwrite: z.boolean().optional().default(false),
},
}, async ({ name, directory, withTest, overwrite }) => {
try {
const react = requireReact();
const typescript = fs.existsSync(path.join(react.root, 'tsconfig.json'));
const ext = typescript ? 'ts' : 'js';
const hookName = name.startsWith('use') ? camelCase(name) : `use${pascalCase(name)}`;
const dir = resolveWorkspacePath(react.root, directory ?? 'src/hooks');
const hookFile = path.join(dir, `${hookName}.${ext}`);
assertNotExists(hookFile, overwrite);
fs.mkdirSync(dir, { recursive: true });
fs.writeFileSync(hookFile, `import { useState } from 'react';
export function ${hookName}() {
const [count, setCount] = useState(0);
return { count, increment: () => setCount((c) => c + 1) };
}
`);
const files = [hookFile];
if (withTest) {
const testFile = path.join(dir, `${hookName}.test.${ext}`);
assertNotExists(testFile, overwrite);
fs.writeFileSync(testFile, `import { describe, expect, it } from 'vitest';
import { renderHook, act } from '@testing-library/react';
import { ${hookName} } from './${hookName}';
describe('${hookName}', () => {
it('increments count', () => {
const { result } = renderHook(() => ${hookName}());
act(() => result.current.increment());
expect(result.current.count).toBe(1);
});
});
`);
files.push(testFile);
}
return jsonResult({ name: hookName, files });
}
catch (error) {
return errorResult(toErrorMessage(error));
}
});
}
function assertNotExists(file, overwrite) {
if (!overwrite && fs.existsSync(file)) {
throw new Error(`"${file}" already exists. Pass overwrite: true to replace it.`);
}
}
//# sourceMappingURL=generateReactHook.js.map