feat: автоматическое определение baseUrl, добавлена документация проекта

This commit is contained in:
2025-10-28 09:18:08 +03:00
parent 15ed8c8b8d
commit 21c7ddfd54
4 changed files with 115 additions and 50 deletions

107
AI-PROJECT-OVERVIEW.md Normal file
View File

@@ -0,0 +1,107 @@
# API CodeGen - Описание проекта для AI агента
## Назначение
CLI утилита для автоматической генерации TypeScript API клиента из OpenAPI/Swagger спецификации.
## Архитектура
### Основные компоненты
1. **CLI** ([`src/cli.ts`](src/cli.ts:1)) - точка входа, парсинг аргументов командной строки
2. **Generator** ([`src/generator.ts`](src/generator.ts:1)) - логика генерации кода через swagger-typescript-api
3. **Config** ([`src/config.ts`](src/config.ts:1)) - валидация конфигурации
4. **Templates** (`src/templates/*.ejs`) - EJS шаблоны для генерации кода
### Технологический стек
- **Runtime**: Bun
- **Языки**: TypeScript
- **Зависимости**:
- `swagger-typescript-api` - основной генератор
- `commander` - парсинг CLI аргументов
- `ejs` - шаблонизатор
- `js-yaml` - парсинг YAML
- `chalk` - цветной вывод в консоль
## Рабочий процесс
1. **Входные данные**: OpenAPI спецификация (JSON/YAML файл или URL)
2. **Обработка**:
- Валидация конфигурации
- Чтение OpenAPI спецификации
- Применение кастомных шаблонов EJS
- Генерация TypeScript кода
3. **Выходные данные**: 3 файла в указанной директории:
- `{FileName}.ts` - API endpoints с методами
- `http-client.ts` - HTTP клиент с настройками
- `data-contracts.ts` - TypeScript типы
## Ключевые особенности
### Кастомизация имен методов
Хук [`onFormatRouteName`](src/generator.ts:76) убирает суффикс "Controller" из имен методов:
- `projectControllerUpdate``update`
- `authControllerLogin``login`
### Извлечение baseUrl
Хук [`onInit`](src/generator.ts:91) автоматически извлекает baseUrl из OpenAPI спецификации (`servers[0].url`)
### Поддержка источников
- Локальные файлы: `./openapi.json`
- URL: `https://api.example.com/openapi.json`
## Использование
```bash
api-codegen -i <путь-к-openapi> -o <выходная-директория> [-n <имя-файла>]
```
### Аргументы
- `-i, --input` - путь к OpenAPI файлу (локальный или URL)
- `-o, --output` - директория для сохранения
- `-n, --name` - опциональное имя файла (по умолчанию из `spec.info.title`)
## Структура проекта
```
src/
├── cli.ts # CLI интерфейс
├── config.ts # Типы и валидация конфигурации
├── generator.ts # Основная логика генерации
├── templates/ # EJS шаблоны
│ ├── api.ejs
│ ├── http-client.ejs
│ ├── data-contracts.ejs
│ └── ...
└── utils/
└── file.ts # Утилиты для работы с файлами
```
## Конфигурация генератора
[`swaggerGenerateApi`](src/generator.ts:38) настроен с параметрами:
- `httpClientType: 'fetch'` - использование Fetch API
- `unwrapResponseData: true` - автоматическая распаковка данных ответа
- `singleHttpClient: true` - единый HTTP клиент
- `extractRequestParams: true` - извлечение параметров запросов
- `extractRequestBody: true` - извлечение тел запросов
- `extractEnums: true` - извлечение enum типов
## Примеры использования сгенерированного кода
```typescript
import { Api, HttpClient } from './Api';
const httpClient = new HttpClient();
httpClient.setSecurityData({ token: 'jwt-token' });
const api = new Api(httpClient);
// Вызов API методов
const user = await api.auth.getProfile();
const result = await api.auth.login({ email, password });
```
## Точки расширения
1. **Шаблоны** - можно модифицировать EJS шаблоны в `src/templates/`
2. **Хуки генератора** - можно добавить новые хуки в [`hooks`](src/generator.ts:75)
3. **Конфигурация** - можно расширить [`GeneratorConfig`](src/config.ts:4) для новых опций

View File

@@ -12,7 +12,6 @@ program
.name('api-codegen')
.description('Generate TypeScript API client from OpenAPI specification')
.version('1.0.0')
.requiredOption('-u, --url <url>', 'Base API URL (e.g., https://api.example.com)')
.requiredOption('-i, --input <path>', 'Path to OpenAPI specification file (JSON or YAML)')
.requiredOption('-o, --output <path>', 'Output directory for generated files')
.option('-n, --name <name>', 'Name of generated file (without extension)')
@@ -20,7 +19,6 @@ program
try {
// Создание конфигурации
const config: Partial<GeneratorConfig> = {
apiUrl: options.url,
inputPath: options.input,
outputPath: options.output,
fileName: options.name,

View File

@@ -2,8 +2,6 @@
* Конфигурация генератора API
*/
export interface GeneratorConfig {
/** Базовый URL API */
apiUrl: string;
/** Путь к файлу OpenAPI спецификации */
inputPath: string;
/** Путь для сохранения сгенерированных файлов */
@@ -18,12 +16,6 @@ export interface GeneratorConfig {
export function validateConfig(config: Partial<GeneratorConfig>): config is GeneratorConfig {
const errors: string[] = [];
if (!config.apiUrl) {
errors.push('API URL is required (--url)');
} else if (!isValidUrl(config.apiUrl)) {
errors.push('API URL must be a valid URL');
}
if (!config.inputPath) {
errors.push('Input path is required (--input)');
}
@@ -39,15 +31,3 @@ export function validateConfig(config: Partial<GeneratorConfig>): config is Gene
return true;
}
/**
* Проверка валидности URL
*/
function isValidUrl(url: string): boolean {
try {
new URL(url);
return true;
} catch {
return false;
}
}

View File

@@ -1,10 +1,9 @@
import { generateApi as swaggerGenerateApi } from 'swagger-typescript-api';
import { resolve, join } from 'path';
import { resolve } from 'path';
import { fileURLToPath } from 'url';
import { dirname } from 'path';
import { unlink } from 'fs/promises';
import type { GeneratorConfig } from './config.js';
import { ensureDir, readJsonFile, writeFileWithDirs } from './utils/file.js';
import { ensureDir, readJsonFile } from './utils/file.js';
const __filename = fileURLToPath(import.meta.url);
const __dirname = dirname(__filename);
@@ -20,7 +19,7 @@ export async function generate(config: GeneratorConfig): Promise<void> {
const templatesPath = resolve(__dirname, '../src/templates');
// Читаем и модифицируем OpenAPI спецификацию
// Читаем OpenAPI спецификацию
const inputPath = config.inputPath.startsWith('http://') || config.inputPath.startsWith('https://')
? config.inputPath
: resolve(config.inputPath);
@@ -34,17 +33,10 @@ export async function generate(config: GeneratorConfig): Promise<void> {
? spec.info.title.replace(/[^a-zA-Z0-9]/g, '')
: 'Api';
}
// Добавляем или обновляем servers с baseUrl
spec.servers = [{ url: config.apiUrl }];
// Сохраняем модифицированную спецификацию во временный файл
const tempSpecPath = join(config.outputPath, '.openapi-temp.json');
await writeFileWithDirs(tempSpecPath, JSON.stringify(spec, null, 2));
try {
await swaggerGenerateApi({
input: tempSpecPath,
input: inputPath,
output: resolve(config.outputPath),
fileName: `${fileName}.ts`,
httpClientType: 'fetch',
@@ -96,10 +88,11 @@ export async function generate(config: GeneratorConfig): Promise<void> {
return templateRouteName;
},
onInit: (configuration, codeGenProcess) => {
// Передаём baseUrl в конфигурацию для шаблонов
onInit: (configuration) => {
// Получаем дефолтный baseUrl из OpenAPI спецификации
const defaultBaseUrl = spec.servers?.[0]?.url || '';
(configuration as any).apiConfig = (configuration as any).apiConfig || {};
(configuration as any).apiConfig.baseUrl = config.apiUrl;
(configuration as any).apiConfig.baseUrl = defaultBaseUrl;
return configuration;
},
},
@@ -109,21 +102,8 @@ export async function generate(config: GeneratorConfig): Promise<void> {
console.log(` - ${fileName}.ts (API endpoints)`);
console.log(' - http-client.ts (HTTP client)');
console.log(' - data-contracts.ts (TypeScript types)');
// Удаляем временный файл
try {
await unlink(tempSpecPath);
} catch (e) {
// Игнорируем ошибки удаления
}
} catch (error) {
console.error('❌ Generation failed:', error);
// Удаляем временный файл даже при ошибке
try {
await unlink(tempSpecPath);
} catch (e) {
// Игнорируем ошибки удаления
}
throw error;
}
}