71 lines
2.9 KiB
JavaScript
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
|