From aaeff0e3fa770bca8256b1d67ec0a6fba6ca17f6 Mon Sep 17 00:00:00 2001 From: domin-mnd Date: Fri, 31 Oct 2025 00:26:53 +0400 Subject: [PATCH 1/3] docs: update README.md & rename .env vars --- CHANGELOG.md | 23 ++++++++++++++++++++ README.md | 41 ++++++++++++++++++++++++++++++----- package.json | 2 +- src/commands/gen/endpoints.ts | 12 +++++----- src/commands/gen/types.ts | 12 +++++----- 5 files changed, 71 insertions(+), 19 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 03d3899..c49f87c 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -5,6 +5,29 @@ Формат основан на [Keep a Changelog](https://keepachangelog.com/ru/1.0.0/), и этот проект придерживается [Semantic Versioning](https://semver.org/lang/ru/). +## [3.0.0] - 2025-10-30 + +### Изменено + +- Обновлена документация для улучшения понимания возможностей пакета +- Названия переменных окружения теперь лучше отражают их назначение + - `GRATIO_TYPES_LINK` → `OPENAPI_TYPES_LINK` + - `GRATIO_TYPES_TOKEN` → `OPENAPI_TYPES_TOKEN` + - `GRATIO_TYPES_OUTPUT` → `OPENAPI_TYPES_OUTPUT` + - `GRATIO_ENDPOINTS_LINK` → `OPENAPI_ENDPOINTS_LINK` + - `GRATIO_ENDPOINTS_TOKEN` → `OPENAPI_ENDPOINTS_TOKEN` + - `GRATIO_ENDPOINTS_OUTPUT` → `OPENAPI_ENDPOINTS_OUTPUT` + +## Структура пакета + +``` +@gratio/api/ +├── LICENSE # Лицензия +├── README.md # Основная документация +├── dist/ # Скомпилированные TypeScript файлы +└── package.json # Конфигурация пакета +``` + ## [2.0.0] - 2025-09-25 ### Добавлено diff --git a/README.md b/README.md index 20ac819..60ff341 100644 --- a/README.md +++ b/README.md @@ -59,6 +59,31 @@ api gen endpoints --link https://api.example.com/openapi.json npx @gratio/api gen endpoints --link ./api/schema.json ``` +## Конфигурация через .env + +Если флаг `--link` не указан, CLI берёт значение из переменных окружения, которые автоматически подгружаются из файла `.env` в корне проекта (используется dotenv). Для команд действуют такие переменные: + +- Для `gen types`: `OPENAPI_TYPES_LINK`, `OPENAPI_TYPES_TOKEN`, `OPENAPI_TYPES_OUTPUT` +- Для `gen endpoints`: `OPENAPI_ENDPOINTS_LINK`, `OPENAPI_ENDPOINTS_TOKEN`, `OPENAPI_ENDPOINTS_OUTPUT` + +Пример файла `.env`: + +``` +# OpenAPI источник (URL или локальный файл) +OPENAPI_TYPES_LINK=https://api.example.com/openapi.json +OPENAPI_ENDPOINTS_LINK=./api/schema.json + +# Токены доступа (нужны для приватных репозиториев GitHub/GitLab) +OPENAPI_TYPES_TOKEN=ghp_your_token_here +OPENAPI_ENDPOINTS_TOKEN=glpat_your_token_here + +# Куда писать результат (по умолчанию выводится в stdout) +OPENAPI_TYPES_OUTPUT=src/types/api.ts +OPENAPI_ENDPOINTS_OUTPUT=src/types/endpoints.ts +``` + +Примечание: если переменные не заданы и включены подсказки (`--prompt` по умолчанию), CLI спросит недостающие значения интерактивно. В CI/production режиме подсказки отключаются. + ## Доступные типы ### Основные типы API @@ -114,11 +139,13 @@ Generates types using OpenAPI 3 specification. Flags: -s, --silent Suppresses all console output except errors. -p, --prompt Whether to prompt for missing information like spec URL or PAT. - -l, --link The URL to the OpenAPI specification (GRATIO_TYPES_LINK). - -P, --token Personal Access Token with api scope (GRATIO_TYPES_TOKEN). - -o, --output The output path for the generated types file (GRATIO_TYPES_OUTPUT). + -l, --link The URL to the OpenAPI specification (OPENAPI_TYPES_LINK). + -P, --token Personal Access Token with api scope (OPENAPI_TYPES_TOKEN). + -o, --output The output path for the generated types file (OPENAPI_TYPES_OUTPUT). ``` +Примечание: значение для `--link` может быть как URL, так и путём к локальному файлу (например, `./api/schema.json`). + ## `@gratio/api gen endpoints --help` ``` @@ -130,11 +157,13 @@ Generates endpoint enum with data from OpenAPI 3 specification. Flags: -s, --silent Suppresses all console output except errors. -p, --prompt Whether to prompt for missing information like spec URL or PAT. - -l, --link The URL to the OpenAPI specification (GRATIO_ENDPOINTS_LINK). - -P, --token Personal Access Token with api scope (GRATIO_ENDPOINTS_TOKEN). - -o, --output The output path for the generated endpoint file (GRATIO_ENDPOINTS_OUTPUT). + -l, --link The URL to the OpenAPI specification (OPENAPI_ENDPOINTS_LINK). + -P, --token Personal Access Token with api scope (OPENAPI_ENDPOINTS_TOKEN). + -o, --output The output path for the generated endpoint file (OPENAPI_ENDPOINTS_OUTPUT). ``` +Примечание: значение для `--link` может быть как URL, так и путём к локальному файлу (например, `./api/schema.json`). + ## Лицензия Этот проект находится под лицензией [MIT](https://choosealicense.com/licenses/mit/). Вы можете свободно использовать его в своих целях. diff --git a/package.json b/package.json index 0810bd8..a864bb5 100644 --- a/package.json +++ b/package.json @@ -1,6 +1,6 @@ { "name": "@gratio/api", - "version": "2.0.0", + "version": "3.0.0", "description": "TypeScript common types for Gratio projects API responses and requests", "homepage": "https://github.com/Gratio-tech/Api#readme", "packageManager": "bun@1.2.17", diff --git a/src/commands/gen/endpoints.ts b/src/commands/gen/endpoints.ts index 7e72b89..718732a 100644 --- a/src/commands/gen/endpoints.ts +++ b/src/commands/gen/endpoints.ts @@ -83,17 +83,17 @@ export default defineCommand({ }, link: { type: 'string', - description: 'The URL to the OpenAPI specification (GRATIO_ENDPOINTS_LINK).', + description: 'The URL to the OpenAPI specification (OPENAPI_ENDPOINTS_LINK).', short: 'l', }, token: { type: 'string', - description: 'Personal Access Token with api scope (GRATIO_ENDPOINTS_TOKEN).', + description: 'Personal Access Token with api scope (OPENAPI_ENDPOINTS_TOKEN).', short: 'P', }, output: { type: 'string', - description: 'The output path for the generated endpoint file (GRATIO_ENDPOINTS_OUTPUT).', + description: 'The output path for the generated endpoint file (OPENAPI_ENDPOINTS_OUTPUT).', short: 'o', default: 'stdout', }, @@ -106,13 +106,13 @@ export default defineCommand({ } try { - const specLink = await getSpecLink(args, 'GRATIO_ENDPOINTS_LINK'); + const specLink = await getSpecLink(args, 'OPENAPI_ENDPOINTS_LINK'); - const specContents = await getSpecContents(specLink, args, 'GRATIO_ENDPOINTS_TOKEN'); + const specContents = await getSpecContents(specLink, args, 'OPENAPI_ENDPOINTS_TOKEN'); const spec = parseSpec(specContents); const paths = getPaths(spec); - const outputPath = args.output ?? process.env.GRATIO_ENDPOINTS_OUTPUT; + const outputPath = args.output ?? process.env.OPENAPI_ENDPOINTS_OUTPUT; const template = createTemplate( [ diff --git a/src/commands/gen/types.ts b/src/commands/gen/types.ts index 136fe16..fa3ea68 100644 --- a/src/commands/gen/types.ts +++ b/src/commands/gen/types.ts @@ -25,17 +25,17 @@ export default defineCommand({ }, link: { type: 'string', - description: 'The URL to the OpenAPI specification (GRATIO_TYPES_LINK).', + description: 'The URL to the OpenAPI specification (OPENAPI_TYPES_LINK).', short: 'l', }, token: { type: 'string', - description: 'Personal Access Token with api scope (GRATIO_TYPES_TOKEN).', + description: 'Personal Access Token with api scope (OPENAPI_TYPES_TOKEN).', short: 'P', }, output: { type: 'string', - description: 'The output path for the generated types file (GRATIO_TYPES_OUTPUT).', + description: 'The output path for the generated types file (OPENAPI_TYPES_OUTPUT).', short: 'o', default: 'stdout', }, @@ -52,15 +52,15 @@ export default defineCommand({ const openApiImport = import('openapi-typescript'); try { - const specLink = await getSpecLink(args, 'GRATIO_TYPES_LINK'); + const specLink = await getSpecLink(args, 'OPENAPI_TYPES_LINK'); - const specContents = await getSpecContents(specLink, args, 'GRATIO_TYPES_TOKEN'); + const specContents = await getSpecContents(specLink, args, 'OPENAPI_TYPES_TOKEN'); const { astToString, default: openapiTS } = await openApiImport; const ast = await openapiTS(specContents); const contents = astToString(ast); - const outputPath = args.output ?? process.env.GRATIO_TYPES_OUTPUT; + const outputPath = args.output ?? process.env.OPENAPI_TYPES_OUTPUT; if (outputPath === 'stdout') { console.log(contents); From 9b9110edb8582c3970836bb9fb6c00a95ecf8909 Mon Sep 17 00:00:00 2001 From: domin-mnd Date: Thu, 6 Nov 2025 12:56:02 +0400 Subject: [PATCH 2/3] feat: parseApiRequest --- CHANGELOG.md | 10 ++++++ README.md | 67 ++++++++++++++++++++++++++++++++++++ package.json | 2 +- src/commands/gen/types.ts | 4 ++- src/index.ts | 1 + src/utils/parseApiRequest.ts | 41 ++++++++++++++++++++++ src/utils/template.ts | 4 ++- 7 files changed, 126 insertions(+), 3 deletions(-) create mode 100644 src/utils/parseApiRequest.ts diff --git a/CHANGELOG.md b/CHANGELOG.md index c49f87c..cb30d89 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -5,6 +5,16 @@ Формат основан на [Keep a Changelog](https://keepachangelog.com/ru/1.0.0/), и этот проект придерживается [Semantic Versioning](https://semver.org/lang/ru/). +## [3.1.0] - 2025-11-06 + +### Добавлено + +- Добавлена утилита `parseApiRequest` для упрощения создания запросов к API на основе спецификации OpenAPI и эндпоинтов + +### Исправлено + +- Исправлена генерация типов в корневой папке проекта при использовании команды генерации типов + ## [3.0.0] - 2025-10-30 ### Изменено diff --git a/README.md b/README.md index 60ff341..e6ac362 100644 --- a/README.md +++ b/README.md @@ -128,6 +128,73 @@ async getEncryptedData(): Promise { } ``` +## Утилита: parseApiRequest + +`parseApiRequest` помогает собрать корректный `Request` для `fetch` из структурированного описания запроса: базовый URL, путь с параметрами, метод, заголовки, тело и query-параметры. + +Импорт: + +```ts +import { parseApiRequest } from '@gratio/api'; +``` + +### Пример: GET с path и query параметрами + +```ts +const request = parseApiRequest({ + baseUrl: 'https://api.example.com', + path: '/users/{id}', + method: 'get', + status: '200', + pathParams: { id: '123' }, + queryParams: { search: 'john', limit: '10' }, + headers: { Authorization: `Bearer ${token}` }, +}); + +const response = await fetch(request); +const data = await response.json(); +``` + +### Пример: POST с JSON-телом + +```ts +type CreateUserDto = { name: string; email: string }; + +const request = parseApiRequest({ + baseUrl: 'https://api.example.com', + path: '/users', + method: 'post', + status: '201', + body: { name: 'John', email: 'john@example.com' } satisfies CreateUserDto, +}); + +const response = await fetch(request); +``` + +### Пример: POST с FormData + +```ts +const form = new FormData(); +form.append('avatar', file); + +const request = parseApiRequest({ + baseUrl: 'https://api.example.com', + path: '/users/{id}/avatar', + method: 'post', + status: '200', + pathParams: { id: '123' }, + body: form, // Content-Type проставлять не нужно — браузер выставит boundary автоматически +}); + +await fetch(request); +``` + +Примечания: + +- Если указан `FormData`, заголовок `Content-Type` не добавляется вручную. +- Для статус-кода `204` тело запроса не отправляется. +- Путь может включать параметры вида `{id}` — они будут подставлены из `pathParams` с URL-экранированием. Query-параметры добавляются из `queryParams`. + ## `@gratio/api gen types --help` ``` diff --git a/package.json b/package.json index a864bb5..78c1cb6 100644 --- a/package.json +++ b/package.json @@ -1,6 +1,6 @@ { "name": "@gratio/api", - "version": "3.0.0", + "version": "3.1.0", "description": "TypeScript common types for Gratio projects API responses and requests", "homepage": "https://github.com/Gratio-tech/Api#readme", "packageManager": "bun@1.2.17", diff --git a/src/commands/gen/types.ts b/src/commands/gen/types.ts index fa3ea68..944faa4 100644 --- a/src/commands/gen/types.ts +++ b/src/commands/gen/types.ts @@ -68,7 +68,9 @@ export default defineCommand({ } if (typeof outputPath === 'string') { - mkdirSync(dirname(outputPath), { recursive: true }); + const dirName = dirname(outputPath); + if (dirName !== '.') mkdirSync(dirName, { recursive: true }); + await writeFile(outputPath, contents); console.info(`✓ ${outputPath}`); } diff --git a/src/index.ts b/src/index.ts index a1b7d34..9223848 100644 --- a/src/index.ts +++ b/src/index.ts @@ -1 +1,2 @@ +export * from '~/utils/parseApiRequest'; export * from '~/types/api'; diff --git a/src/utils/parseApiRequest.ts b/src/utils/parseApiRequest.ts new file mode 100644 index 0000000..bca9d14 --- /dev/null +++ b/src/utils/parseApiRequest.ts @@ -0,0 +1,41 @@ +interface ApiResponse { + path: string; + method: string; + status: string; +} + +interface ApiRequest extends ApiResponse { + baseUrl: string; + headers?: Record; + body?: Record; + queryParams?: Record; + pathParams?: Record; +} + +/** Parse ApiRequest to a Request object. */ +export function parseApiRequest(request: ApiRequest): Request { + // Parse query parameters + const url = new URL(request.path, request.baseUrl); + if (request.queryParams) { + for (const [key, value] of Object.entries(request.queryParams)) { + url.searchParams.append(key, String(value)); + } + } + + // Parse path parameters + for (const [key, value] of Object.entries(request.pathParams || {})) { + url.pathname = url.pathname.replace(`{${key}}`, encodeURIComponent(String(value))); + } + + // Parse headers + const headers = new Headers(request.headers || {}); + if (request.status !== '204' && !(request.body instanceof FormData)) { + headers.append('Content-Type', 'application/json'); + } + + return new Request(request.path, { + method: request.method.toUpperCase(), + headers, + body: request.body instanceof FormData ? request.body : JSON.stringify(request.body), + }); +} diff --git a/src/utils/template.ts b/src/utils/template.ts index 598d3aa..078a441 100644 --- a/src/utils/template.ts +++ b/src/utils/template.ts @@ -12,7 +12,9 @@ export function createTemplate(path: string, isRaw = false) { const write = (outputPath: string): void => { try { - mkdirSync(dirname(outputPath), { recursive: true }); + const dirName = dirname(outputPath); + if (dirName !== '.') mkdirSync(dirName, { recursive: true }); + writeFileSync(outputPath, template); console.info(`✓ ${outputPath}`); } catch { From 56fa4ce1bf8eb6634249ed888181b456ddaef0cd Mon Sep 17 00:00:00 2001 From: domin-mnd Date: Fri, 7 Nov 2025 13:26:15 +0400 Subject: [PATCH 3/3] fix(parseApiRequest): typing --- README.md | 1 - src/utils/parseApiRequest.ts | 8 ++++---- 2 files changed, 4 insertions(+), 5 deletions(-) diff --git a/README.md b/README.md index e6ac362..ca2c480 100644 --- a/README.md +++ b/README.md @@ -192,7 +192,6 @@ await fetch(request); Примечания: - Если указан `FormData`, заголовок `Content-Type` не добавляется вручную. -- Для статус-кода `204` тело запроса не отправляется. - Путь может включать параметры вида `{id}` — они будут подставлены из `pathParams` с URL-экранированием. Query-параметры добавляются из `queryParams`. ## `@gratio/api gen types --help` diff --git a/src/utils/parseApiRequest.ts b/src/utils/parseApiRequest.ts index bca9d14..d9d59a4 100644 --- a/src/utils/parseApiRequest.ts +++ b/src/utils/parseApiRequest.ts @@ -7,7 +7,7 @@ interface ApiResponse { interface ApiRequest extends ApiResponse { baseUrl: string; headers?: Record; - body?: Record; + body?: Record | FormData; queryParams?: Record; pathParams?: Record; } @@ -29,13 +29,13 @@ export function parseApiRequest(request: ApiRequest): Request { // Parse headers const headers = new Headers(request.headers || {}); - if (request.status !== '204' && !(request.body instanceof FormData)) { + if (request.body && !(request.body instanceof FormData)) { headers.append('Content-Type', 'application/json'); } - return new Request(request.path, { + return new Request(url.toString(), { method: request.method.toUpperCase(), headers, - body: request.body instanceof FormData ? request.body : JSON.stringify(request.body), + body: request.body ? (request.body instanceof FormData ? request.body : JSON.stringify(request.body)) : undefined, }); }