feat: добавить split-режим генерации REST-клиента
- добавлен режим генерации single, split и both - добавлены отдельные operation-файлы и createApiClient - удалена генерация SWR-хуков и зависимости React/SWR - обновлены CLI, шаблоны, примеры, документация и тесты - версия пакета повышена до 3.0.0
This commit is contained in:
@@ -49,23 +49,28 @@ describe('E2E Generation', () => {
|
||||
FIXTURES.VALID,
|
||||
'--output',
|
||||
outputPath,
|
||||
'--name',
|
||||
'TestApi',
|
||||
'--mode',
|
||||
'split',
|
||||
]);
|
||||
|
||||
expect(exitCode).toBe(0);
|
||||
|
||||
// 2. Проверка создания файла
|
||||
const generatedFile = join(outputPath, 'TestApi.ts');
|
||||
const generatedFile = join(outputPath, 'index.ts');
|
||||
const exists = await fileExists(generatedFile);
|
||||
expect(exists).toBe(true);
|
||||
|
||||
// 3. Проверка импорта (компиляция TypeScript)
|
||||
const testFile = join(tempDir, 'test-import.ts');
|
||||
const testCode = `
|
||||
import { Api } from '${generatedFile}';
|
||||
|
||||
const api = new Api();
|
||||
import { createApiClient, HttpClient } from '${generatedFile}';
|
||||
import { getAll } from '${join(outputPath, 'operations', 'get-all.ts')}';
|
||||
|
||||
const api = createApiClient(new HttpClient(), {
|
||||
users: {
|
||||
getAll,
|
||||
},
|
||||
});
|
||||
console.log('Import successful');
|
||||
`;
|
||||
|
||||
@@ -97,24 +102,7 @@ describe('E2E Generation', () => {
|
||||
|
||||
test('повторная генерация (перезапись файлов)', async () => {
|
||||
const outputPath = join(tempDir, 'output');
|
||||
const fileName = 'TestApi';
|
||||
|
||||
// Первая генерация
|
||||
await execa('bun', [
|
||||
'run',
|
||||
CLI_PATH,
|
||||
'--input',
|
||||
FIXTURES.MINIMAL,
|
||||
'--output',
|
||||
outputPath,
|
||||
'--name',
|
||||
fileName,
|
||||
]);
|
||||
|
||||
const generatedFile = join(outputPath, `${fileName}.ts`);
|
||||
const firstContent = await readTextFile(generatedFile);
|
||||
|
||||
// Вторая генерация (перезапись)
|
||||
// Первая генерация с endpoints
|
||||
await execa('bun', [
|
||||
'run',
|
||||
CLI_PATH,
|
||||
@@ -122,8 +110,25 @@ describe('E2E Generation', () => {
|
||||
FIXTURES.VALID,
|
||||
'--output',
|
||||
outputPath,
|
||||
'--name',
|
||||
fileName,
|
||||
'--mode',
|
||||
'split',
|
||||
]);
|
||||
|
||||
const generatedFile = join(outputPath, 'operations', 'index.ts');
|
||||
const staleOperationFile = join(outputPath, 'operations', 'get-all.ts');
|
||||
const firstContent = await readTextFile(generatedFile);
|
||||
expect(await fileExists(staleOperationFile)).toBe(true);
|
||||
|
||||
// Вторая генерация (перезапись меньшей схемой)
|
||||
await execa('bun', [
|
||||
'run',
|
||||
CLI_PATH,
|
||||
'--input',
|
||||
FIXTURES.MINIMAL,
|
||||
'--output',
|
||||
outputPath,
|
||||
'--mode',
|
||||
'split',
|
||||
]);
|
||||
|
||||
const secondContent = await readTextFile(generatedFile);
|
||||
@@ -134,6 +139,7 @@ describe('E2E Generation', () => {
|
||||
// Файл должен существовать
|
||||
const exists = await fileExists(generatedFile);
|
||||
expect(exists).toBe(true);
|
||||
expect(await fileExists(staleOperationFile)).toBe(false);
|
||||
}, 60000);
|
||||
|
||||
test('генерация из HTTP URL', async () => {
|
||||
@@ -147,50 +153,41 @@ describe('E2E Generation', () => {
|
||||
'https://petstore3.swagger.io/api/v3/openapi.json',
|
||||
'--output',
|
||||
outputPath,
|
||||
'--name',
|
||||
'PetStore',
|
||||
'--mode',
|
||||
'split',
|
||||
]);
|
||||
|
||||
expect(exitCode).toBe(0);
|
||||
|
||||
const generatedFile = join(outputPath, 'PetStore.ts');
|
||||
const generatedFile = join(outputPath, 'index.ts');
|
||||
const exists = await fileExists(generatedFile);
|
||||
expect(exists).toBe(true);
|
||||
|
||||
// Проверяем что файл не пустой
|
||||
const content = await readTextFile(generatedFile);
|
||||
const content = await readTextFile(join(outputPath, 'http-client.ts'));
|
||||
expect(content.length).toBeGreaterThan(1000);
|
||||
}, 60000);
|
||||
|
||||
test('генерация с флагом --swr', async () => {
|
||||
test('флаг --swr больше не поддерживается', async () => {
|
||||
const outputPath = join(tempDir, 'output');
|
||||
|
||||
const { exitCode } = await execa('bun', [
|
||||
'run',
|
||||
CLI_PATH,
|
||||
'--input',
|
||||
FIXTURES.VALID,
|
||||
'--output',
|
||||
outputPath,
|
||||
'--name',
|
||||
'SwrApi',
|
||||
'--swr',
|
||||
]);
|
||||
|
||||
expect(exitCode).toBe(0);
|
||||
|
||||
const generatedFile = join(outputPath, 'SwrApi.ts');
|
||||
const content = await readTextFile(generatedFile);
|
||||
|
||||
// Проверяем наличие импорта useSWR
|
||||
expect(content).toContain('import useSWR from "swr"');
|
||||
|
||||
// Проверяем наличие use* хуков для GET запросов
|
||||
expect(content).toContain('useGetAll');
|
||||
expect(content).toContain('useGetById');
|
||||
try {
|
||||
await execa('bun', [
|
||||
'run',
|
||||
CLI_PATH,
|
||||
'--input',
|
||||
FIXTURES.VALID,
|
||||
'--output',
|
||||
outputPath,
|
||||
'--swr',
|
||||
]);
|
||||
throw new Error('Should have thrown');
|
||||
} catch (error: any) {
|
||||
expect(error.exitCode).not.toBe(0);
|
||||
}
|
||||
}, 30000);
|
||||
|
||||
test('генерация без флага --swr не содержит хуки', async () => {
|
||||
test('REST генерация не содержит SWR хуки', async () => {
|
||||
const outputPath = join(tempDir, 'output');
|
||||
|
||||
const { exitCode } = await execa('bun', [
|
||||
@@ -200,13 +197,13 @@ describe('E2E Generation', () => {
|
||||
FIXTURES.VALID,
|
||||
'--output',
|
||||
outputPath,
|
||||
'--name',
|
||||
'NoSwrApi',
|
||||
'--mode',
|
||||
'split',
|
||||
]);
|
||||
|
||||
expect(exitCode).toBe(0);
|
||||
|
||||
const generatedFile = join(outputPath, 'NoSwrApi.ts');
|
||||
const generatedFile = join(outputPath, 'http-client.ts');
|
||||
const content = await readTextFile(generatedFile);
|
||||
|
||||
// Проверяем отсутствие импорта useSWR
|
||||
@@ -229,16 +226,21 @@ describe('E2E Generation', () => {
|
||||
FIXTURES.VALID,
|
||||
'--output',
|
||||
outputPath,
|
||||
'--name',
|
||||
'TestApi',
|
||||
'--mode',
|
||||
'split',
|
||||
]);
|
||||
|
||||
// Динамически импортируем сгенерированный API
|
||||
const generatedFile = join(outputPath, 'TestApi.ts');
|
||||
const { Api } = await import(generatedFile);
|
||||
|
||||
const api = new Api();
|
||||
const result = await api.users.getAll();
|
||||
const generatedFile = join(outputPath, 'index.ts');
|
||||
const { createApiClient, HttpClient } = await import(generatedFile);
|
||||
const { getAll } = await import(join(outputPath, 'operations', 'get-all.ts'));
|
||||
|
||||
const api = createApiClient(new HttpClient(), {
|
||||
users: {
|
||||
getAll,
|
||||
},
|
||||
});
|
||||
const result = await api.users.getAll({});
|
||||
|
||||
expect(Array.isArray(result)).toBe(true);
|
||||
expect(result.length).toBe(2);
|
||||
@@ -254,15 +256,20 @@ describe('E2E Generation', () => {
|
||||
FIXTURES.VALID,
|
||||
'--output',
|
||||
outputPath,
|
||||
'--name',
|
||||
'TestApi',
|
||||
'--mode',
|
||||
'split',
|
||||
]);
|
||||
|
||||
// Динамически импортируем сгенерированный API
|
||||
const generatedFile = join(outputPath, 'TestApi.ts');
|
||||
const { Api } = await import(generatedFile);
|
||||
|
||||
const api = new Api();
|
||||
const generatedFile = join(outputPath, 'index.ts');
|
||||
const { createApiClient, HttpClient } = await import(generatedFile);
|
||||
const { create } = await import(join(outputPath, 'operations', 'create.ts'));
|
||||
|
||||
const api = createApiClient(new HttpClient(), {
|
||||
users: {
|
||||
create,
|
||||
},
|
||||
});
|
||||
const result = await api.users.create({
|
||||
email: 'new@example.com',
|
||||
password: 'password123'
|
||||
@@ -282,17 +289,22 @@ describe('E2E Generation', () => {
|
||||
FIXTURES.VALID,
|
||||
'--output',
|
||||
outputPath,
|
||||
'--name',
|
||||
'TestApi',
|
||||
'--mode',
|
||||
'split',
|
||||
]);
|
||||
|
||||
const testFile = join(tempDir, 'test-404.ts');
|
||||
const testCode = `
|
||||
import { Api } from '${join(outputPath, 'TestApi.ts')}';
|
||||
|
||||
const api = new Api();
|
||||
import { createApiClient, HttpClient } from '${join(outputPath, 'index.ts')}';
|
||||
import { getById } from '${join(outputPath, 'operations', 'get-by-id.ts')}';
|
||||
|
||||
const api = createApiClient(new HttpClient(), {
|
||||
users: {
|
||||
getById,
|
||||
},
|
||||
});
|
||||
try {
|
||||
await api.users.getById('999');
|
||||
await api.users.getById({ id: '999' });
|
||||
} catch (error) {
|
||||
console.log('error');
|
||||
}
|
||||
@@ -314,14 +326,16 @@ describe('E2E Generation', () => {
|
||||
FIXTURES.WITH_AUTH,
|
||||
'--output',
|
||||
outputPath,
|
||||
'--name',
|
||||
'AuthApi',
|
||||
'--mode',
|
||||
'split',
|
||||
]);
|
||||
|
||||
// Динамически импортируем сгенерированный API
|
||||
const generatedFile = join(outputPath, 'AuthApi.ts');
|
||||
const { Api, HttpClient } = await import(generatedFile);
|
||||
|
||||
const generatedFile = join(outputPath, 'index.ts');
|
||||
const { createApiClient, HttpClient } = await import(generatedFile);
|
||||
const { login } = await import(join(outputPath, 'operations', 'login.ts'));
|
||||
const { get } = await import(join(outputPath, 'operations', 'get.ts'));
|
||||
|
||||
// Создаем HttpClient с securityWorker для добавления Bearer токена
|
||||
const httpClient = new HttpClient({
|
||||
securityWorker: (securityData: string | null) => {
|
||||
@@ -334,8 +348,15 @@ describe('E2E Generation', () => {
|
||||
}
|
||||
}
|
||||
});
|
||||
|
||||
const api = new Api(httpClient);
|
||||
|
||||
const api = createApiClient(httpClient, {
|
||||
auth: {
|
||||
login,
|
||||
},
|
||||
profile: {
|
||||
get,
|
||||
},
|
||||
});
|
||||
|
||||
// Логин
|
||||
const loginResult = await api.auth.login({
|
||||
@@ -352,4 +373,4 @@ describe('E2E Generation', () => {
|
||||
expect(profile.email).toBe('test@example.com');
|
||||
}, 60000);
|
||||
});
|
||||
});
|
||||
});
|
||||
|
||||
Reference in New Issue
Block a user