@paulpugovkin/api-docs-axios-ts-generator 1.1.0 → 1.2.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
@@ -1,264 +0,0 @@
1
- const path = require("path");
2
- const {resolveType} = require("../resolve-type/resolveType");
3
- const {generateJSDoc} = require("../generate-js-doc/generateJSDoc");
4
-
5
- /**
6
- * Генерирует метод TypeScript на основе спецификации API.
7
- * @param {string} name - Имя метода.
8
- * @param {Object} method - Описание метода API (из OpenAPI).
9
- * @param {string} path - Путь API.
10
- * @param {string} methodType - HTTP-метод (GET, POST и т.д.).
11
- * @param {Object<string, string>} schemaRefs - Ссылки на схемы для разрешения типов.
12
- * @param {Set<string>} usedInterfaces - Набор интерфейсов, используемых в методе.
13
- * @param {Object} config - Конфигурация генератора.
14
- * @returns {string} - Сгенерированный метод TypeScript.
15
- */
16
- function generateMethod(
17
- name,
18
- method,
19
- path,
20
- methodType,
21
- schemaRefs,
22
- usedInterfaces,
23
- config = {}
24
- ) {
25
- const allowConfigSpread = config?.options?.allowAxiosConfigSpread !== false;
26
- const pathParams = [...path.matchAll(/\{(\w+)\}/g)].map((match) => match[1]);
27
- pathParams.forEach((param) => {
28
- path = path.replace(`{${param}}`, `\${${param}}`);
29
- });
30
-
31
- const requiredParams = [];
32
- const requiredQueryParams = [];
33
- const optionalQueryParams = [];
34
- const nestedParams = {};
35
- let bodyParam = null;
36
- let bodyString = "";
37
- let isMultipart = false;
38
-
39
- // Обработка параметров запроса
40
- for (const param of method.parameters || []) {
41
- const paramName = param.name;
42
- const paramLocation = param.in;
43
- const paramRequired = param.required || false;
44
- const paramSchema = param.schema || {};
45
-
46
- // Проверяем, является ли параметр сложным объектом (имеет $ref или properties)
47
- const isComplexObject = paramSchema.$ref || paramSchema.properties;
48
-
49
- if (paramLocation === "path") {
50
- const paramType = resolveType(paramSchema, schemaRefs, usedInterfaces);
51
- requiredParams.push(`${paramName}: ${paramType}`);
52
- } else if (paramLocation === "query") {
53
- if (isComplexObject) {
54
- // Для сложных объектов добавляем распыление
55
- const paramType = resolveType(paramSchema, schemaRefs, usedInterfaces);
56
- if (paramRequired) {
57
- requiredParams.push(`${paramName}: ${paramType}`);
58
- // Вместо добавления в requiredQueryParams, просто отмечаем что есть сложный объект
59
- // который нужно распылить
60
- requiredQueryParams.push(`...${paramName}`);
61
- } else {
62
- optionalQueryParams.push(`${paramName}?: ${paramType}`);
63
- // Для опциональных сложных объектов тоже добавляем распыление
64
- requiredQueryParams.push(`...(${paramName} || {})`);
65
- }
66
- } else if (paramName.includes(".")) {
67
- const [nestedName, subfield] = paramName.split(".");
68
- nestedParams[nestedName] = nestedParams[nestedName] || [];
69
- const paramType = resolveType(paramSchema, schemaRefs, usedInterfaces);
70
- nestedParams[nestedName].push(`${subfield}?: ${paramType}`);
71
- } else {
72
- const paramType = resolveType(paramSchema, schemaRefs, usedInterfaces);
73
- if (paramRequired) {
74
- requiredQueryParams.push(paramName);
75
- requiredParams.push(`${paramName}: ${paramType}`);
76
- } else {
77
- optionalQueryParams.push(`${paramName}?: ${paramType}`);
78
- }
79
- }
80
- }
81
- }
82
-
83
- // Обрабатываем вложенные параметры (только для простых типов)
84
- for (const [nestedName, fields] of Object.entries(nestedParams)) {
85
- optionalQueryParams.push(`${nestedName}?: { ${fields.join(", ")} }`);
86
- }
87
-
88
- // Проверяем наличие тела запроса (только для методов, которые могут иметь тело)
89
- const methodsWithBody = ['post', 'put', 'patch', 'delete'];
90
- const hasRequestBody = methodsWithBody.includes(methodType.toLowerCase()) &&
91
- method.requestBody &&
92
- method.requestBody.content &&
93
- Object.keys(method.requestBody.content).length > 0;
94
-
95
- if (hasRequestBody) {
96
- const content = method.requestBody.content || {};
97
- if (content["application/json"]) {
98
- const schema = content["application/json"].schema || {};
99
- if (schema.oneOf) {
100
- const refName = resolveType(schema, schemaRefs, usedInterfaces);
101
- bodyParam = `values: ${refName}`;
102
- } else if (schema.$ref) {
103
- const refName = schema.$ref.split("/").pop();
104
- usedInterfaces.add(refName);
105
- bodyParam = `values: ${refName}`;
106
- } else if (schema.properties) {
107
- const props = Object.entries(schema.properties)
108
- .map(
109
- ([key, value]) =>
110
- `${key}: ${resolveType(value, schemaRefs, usedInterfaces)}`
111
- )
112
- .join(", ");
113
- bodyParam = `values: { ${props} }`;
114
- } else {
115
- bodyParam = 'values: any'
116
- }
117
- bodyString = "values";
118
- } else if (content["multipart/form-data"]) {
119
- isMultipart = true;
120
- const schema = content["multipart/form-data"].schema || {};
121
- if (schema.properties) {
122
- for (const [fieldName, fieldDetails] of Object.entries(
123
- schema.properties
124
- )) {
125
- let tsType = resolveType(fieldDetails, schemaRefs, usedInterfaces);
126
- if (fieldDetails.format === "binary") {
127
- tsType = "File";
128
- }
129
- }
130
- }
131
- bodyParam = `data: FormData`;
132
- bodyString = "data";
133
- }
134
- }
135
-
136
- let responseType = "void";
137
- if (method.responses) {
138
- for (const [status, response] of Object.entries(method.responses)) {
139
- if (response.content) {
140
- const contentSchema = Object.values(response.content)[0].schema || {};
141
- if (contentSchema.$ref) {
142
- const refName = contentSchema.$ref.split("/").pop();
143
- responseType = refName;
144
- usedInterfaces.add(refName);
145
- }
146
- }
147
- }
148
- }
149
-
150
- const returnType = `Promise<AxiosResponse<${responseType}>>`;
151
- const optionalArgsName = optionalQueryParams.length ? "queryParams" : null;
152
- const optionalArgsDefinition = optionalArgsName
153
- ? `${optionalArgsName}: { ${optionalQueryParams.join(", ")} }`
154
- : "";
155
-
156
- // Определяем, есть ли параметры запроса (query params)
157
- const hasQueryParams = requiredQueryParams.length > 0 || optionalQueryParams.length > 0;
158
-
159
- const jsDocComment = generateJSDoc(method);
160
-
161
- // Строки для аргументов функции
162
- const functionArgs = `(${[
163
- ...requiredParams,
164
- bodyParam,
165
- optionalArgsDefinition,
166
- "config?: AxiosRequestConfig",
167
- ]
168
- .filter(Boolean)
169
- .join(", ")}): ${returnType}`;
170
- const functionString = `${name}${functionArgs}`;
171
-
172
- // Строка url для запроса
173
- const fullUrlString = `const fullURL = \`\${BASE_URL}${path}\`;`;
174
-
175
- // Формируем строку для axios вызова
176
- let axiosString = '';
177
-
178
- // Функция для формирования объекта параметров
179
- const buildParamsObject = () => {
180
- const parts = [];
181
-
182
- // Добавляем обязательные параметры (простые и распыленные сложные)
183
- if (requiredQueryParams.length > 0) {
184
- parts.push(...requiredQueryParams);
185
- }
186
-
187
- // Добавляем опциональные параметры
188
- if (optionalArgsName) {
189
- if (parts.length > 0) {
190
- parts.push(`...${optionalArgsName}`);
191
- } else {
192
- parts.push(`...${optionalArgsName}`);
193
- }
194
- }
195
-
196
- return parts.length > 0 ? `{ ${parts.join(', ')} }` : '{}';
197
- };
198
-
199
- if (methodType.toLowerCase() === "delete") {
200
- // DELETE запросы могут иметь и тело, и параметры запроса
201
- if (bodyParam) {
202
- // Есть тело запроса
203
- if (hasQueryParams) {
204
- // Есть и тело, и параметры запроса
205
- const paramsObject = buildParamsObject();
206
- axiosString = `return axios.delete(fullURL, { data: values, ...config, params: ${paramsObject} });`;
207
- } else {
208
- // Только тело запроса, без параметров
209
- axiosString = `return axios.delete(fullURL, { data: values, ...config });`;
210
- }
211
- } else {
212
- // Нет тела запроса
213
- if (hasQueryParams) {
214
- // Только параметры запроса
215
- const paramsObject = buildParamsObject();
216
- axiosString = `return axios.delete(fullURL, { ...config, params: ${paramsObject} });`;
217
- } else {
218
- // Нет ни тела, ни параметров
219
- axiosString = `return axios.delete(fullURL, config);`;
220
- }
221
- }
222
- } else if (methodType.toLowerCase() === "get") {
223
- // GET запросы никогда не имеют тела
224
- if (hasQueryParams) {
225
- const paramsObject = buildParamsObject();
226
- axiosString = `return axios.get(fullURL, { ...config, params: ${paramsObject} });`;
227
- } else {
228
- axiosString = `return axios.get(fullURL, config);`;
229
- }
230
- } else {
231
- // PUT/POST/PATCH методы
232
- if (hasRequestBody) {
233
- // Есть тело запроса
234
- if (hasQueryParams) {
235
- // Есть и тело, и параметры запроса
236
- const paramsObject = buildParamsObject();
237
- axiosString = `return axios.${methodType}(fullURL, ${isMultipart ? 'data' : bodyString}, { ...config, params: ${paramsObject} });`;
238
- } else {
239
- // Только тело запроса, без параметров
240
- axiosString = `return axios.${methodType}(fullURL, ${isMultipart ? 'data' : bodyString}, config);`;
241
- }
242
- } else {
243
- // Нет тела запроса, но могут быть параметры
244
- if (hasQueryParams) {
245
- // Только параметры запроса
246
- const paramsObject = buildParamsObject();
247
- axiosString = `return axios.${methodType}(fullURL, null, { ...config, params: ${paramsObject} });`;
248
- } else {
249
- // Нет ни тела, ни параметров
250
- axiosString = `return axios.${methodType}(fullURL, null, config);`;
251
- }
252
- }
253
- }
254
-
255
- return `
256
- ${jsDocComment}
257
- ${functionString} {
258
- ${fullUrlString}
259
- ${axiosString}
260
- }
261
- `;
262
- }
263
-
264
- module.exports = {generateMethod};
@@ -1,79 +0,0 @@
1
- /**
2
- * Генератор конфигурации axios
3
- */
4
- const fs = require('fs');
5
- const path = require('path');
6
-
7
- /**
8
- * Генерирует конфигурацию axios для сгенерированного API клиента
9
- * @param {Object} config - Конфигурация генератора
10
- * @param {string} outputDir - Директория вывода
11
- */
12
- function generateAxiosConfig(config, outputDir) {
13
- const axiosDir = path.join(outputDir, 'config', 'axios');
14
- fs.mkdirSync(axiosDir, { recursive: true });
15
-
16
- const axiosConfig = config.axios || {};
17
- const baseURL = axiosConfig.baseURL || '';
18
- const timeout = axiosConfig.timeout || 30000;
19
- const headers = axiosConfig.headers || { 'Content-Type': 'application/json' };
20
- const withCredentials = axiosConfig.withCredentials || false;
21
-
22
- // Генерация файла axios.ts
23
- const axiosTsContent = `import axios, { AxiosInstance, AxiosRequestConfig } from 'axios';
24
-
25
- export const BASE_URL = '${baseURL}';
26
-
27
- const axiosConfig: AxiosRequestConfig = {
28
- baseURL: BASE_URL,
29
- timeout: ${timeout},
30
- headers: ${JSON.stringify(headers, null, 2)},
31
- withCredentials: ${withCredentials},
32
- };
33
-
34
- const axiosInstance: AxiosInstance = axios.create(axiosConfig);
35
-
36
- // Перехватчик запросов
37
- axiosInstance.interceptors.request.use(
38
- (config) => {
39
- // Можно добавить токен авторизации
40
- // const token = localStorage.getItem('token');
41
- // if (token) {
42
- // config.headers.Authorization = \`Bearer \${token}\`;
43
- // }
44
- return config;
45
- },
46
- (error) => {
47
- return Promise.reject(error);
48
- }
49
- );
50
-
51
- // Перехватчик ответов
52
- axiosInstance.interceptors.response.use(
53
- (response) => {
54
- return response;
55
- },
56
- (error) => {
57
- // Обработка ошибок
58
- if (error.response) {
59
- // Сервер ответил со статусом, отличным от 2xx
60
- console.error('Response error:', error.response.status, error.response.data);
61
- } else if (error.request) {
62
- // Запрос был сделан, но ответ не получен
63
- console.error('Request error:', error.request);
64
- } else {
65
- // Произошла ошибка при настройке запроса
66
- console.error('Error:', error.message);
67
- }
68
- return Promise.reject(error);
69
- }
70
- );
71
-
72
- export default axiosInstance;
73
- `;
74
-
75
- fs.writeFileSync(path.join(axiosDir, 'axios.ts'), axiosTsContent, { encoding: 'utf-8' });
76
- console.log(`Generated axios config at: ${axiosDir}/axios.ts`);
77
- }
78
-
79
- module.exports = { generateAxiosConfig };
package/src/index.js DELETED
@@ -1,187 +0,0 @@
1
- const {program} = require("commander");
2
-
3
- // Модули генератора
4
- const {
5
- cleanGeneratedFolder,
6
- } = require("./clean-generated-folder/cleanGeneratedFolder");
7
- const {
8
- updateApiDocsJson,
9
- } = require("./update-api-docs-json/updateApiDocsJson");
10
- const {parseAndGenerate} = require("./parse-and-generate/parseAndGenerate");
11
- const {
12
- generateIndexFileWithOpenApi,
13
- } = require("./generate-index-file-with-public-api/generateIndexFileWithPublicApi");
14
- const {
15
- generateMainIndexFile,
16
- } = require("./generate-main-index-file/generateMainIndexFile");
17
- const {generateAxiosConfig} = require("./generators/axiosConfigGenerator");
18
-
19
- // Simple configuration loader
20
- const fs = require('fs');
21
- const path = require('path');
22
-
23
- async function loadConfig(configPath, cliOptions = {}) {
24
- const resolvedPath = path.resolve(configPath);
25
-
26
- if (!fs.existsSync(resolvedPath)) {
27
- throw new Error(`Configuration file not found: ${resolvedPath}`);
28
- }
29
-
30
- const ext = path.extname(resolvedPath);
31
- let config;
32
-
33
- try {
34
- if (ext === '.js') {
35
- const modulePath = resolvedPath;
36
- config = require(modulePath);
37
- } else if (ext === '.json') {
38
- const content = fs.readFileSync(resolvedPath, 'utf8');
39
- config = JSON.parse(content);
40
- } else if (ext === '.ts') {
41
- const ts = require('typescript');
42
- const content = fs.readFileSync(resolvedPath, 'utf8');
43
- const result = ts.transpileModule(content, {
44
- compilerOptions: {
45
- module: 1, // CommonJS
46
- target: 99, // ESNext
47
- esModuleInterop: true,
48
- },
49
- });
50
- const modulePath = resolvedPath.replace('.ts', '.js');
51
- fs.writeFileSync(modulePath, result.outputText);
52
- config = require(modulePath);
53
- } else {
54
- throw new Error(`Unsupported configuration file format. Use .js, .json, or .ts`);
55
- }
56
- } catch (error) {
57
- throw new Error(`Failed to load configuration: ${error.message}`);
58
- }
59
-
60
- // Merge CLI options with config, filtering out undefined values
61
- const mergedConfig = { ...config };
62
- Object.keys(cliOptions).forEach(key => {
63
- if (cliOptions[key] !== undefined) {
64
- mergedConfig[key] = cliOptions[key];
65
- }
66
- });
67
-
68
- return mergedConfig;
69
- }
70
-
71
- // Основной инструмент CLI
72
- program
73
- .version("1.0.0")
74
- .description("Generate TypeScript API client from OpenAPI documentation")
75
- .option("-c, --config <path>", "Path to configuration file")
76
- .option("--api-docs-url <url>", "URL to fetch the OpenAPI documentation")
77
- .option("--api-docs-path <path>", "Local path to OpenAPI documentation file")
78
- .option("--output-dir <dir>", "Output directory for generated files")
79
- .option("--clean", "Clean output directory before generation")
80
- .parse(process.argv);
81
-
82
- // Основной вызов
83
- async function main() {
84
- const options = program.opts();
85
- let configPath = options.config;
86
-
87
- // Автоматическое обнаружение конфигурационного файла, если он не указан явно
88
- if (!configPath) {
89
- const defaultConfigs = [
90
- 'api-docs-generator.config.js',
91
- 'api-docs-generator.config.json',
92
- 'api-docs-generator.config.ts'
93
- ];
94
-
95
- for (const file of defaultConfigs) {
96
- const filePath = path.join(process.cwd(), file);
97
- if (fs.existsSync(filePath)) {
98
- configPath = filePath;
99
- console.log(`Auto-detected configuration file: ${file}`);
100
- break;
101
- }
102
- }
103
- }
104
-
105
- let config = null;
106
-
107
- // Загрузка конфигурации из файла или использование CLI аргументов
108
- if (configPath) {
109
- try {
110
- config = await loadConfig(configPath, {
111
- apiDocsUrl: options.apiDocsUrl,
112
- apiDocsPath: options.apiDocsPath,
113
- outputDir: options.outputDir,
114
- });
115
- } catch (error) {
116
- console.error(`Failed to load configuration: ${error.message}`);
117
- process.exit(1);
118
- }
119
- } else {
120
- // Режим обратной совместимости - использование CLI аргументов
121
- const apiDocsUrl = options.apiDocsUrl;
122
- const outputDir = options.outputDir || path.resolve(process.cwd(), "generated");
123
-
124
- // Проверяем, указан ли URL для API документации
125
- if (!apiDocsUrl) {
126
- console.error('Error: API documentation URL is required.');
127
- console.error('Please specify --api-docs-url or --api-docs-path, or create a configuration file.');
128
- console.error('Run "api-docs-generator --help" for more information.');
129
- process.exit(1);
130
- }
131
-
132
- config = {
133
- apiDocsUrl: apiDocsUrl,
134
- outputDir: outputDir,
135
- interfacesDir: path.join(outputDir, "interfaces"),
136
- classesDir: path.join(outputDir, "classes"),
137
- groupBy: "tag",
138
- classMode: "multiple",
139
- options: {
140
- cleanOutputDir: options.clean !== undefined ? options.clean : true,
141
- generateAxiosConfig: false,
142
- generateIndexFiles: true,
143
- },
144
- };
145
- }
146
-
147
- console.log(`Using API Docs URL: ${config.apiDocsUrl}`);
148
- console.log(`Output directory: ${config.outputDir || 'Not specified - using default'}`);
149
-
150
- // Определяем пути
151
- const outputDir = config.outputDir || path.resolve(process.cwd(), "generated");
152
- const interfacesDir = config.interfacesDir || path.join(outputDir, "interfaces");
153
- const classesDir = config.classesDir || path.join(outputDir, "classes");
154
- const interfacesOpenApi = path.join(interfacesDir, "index.ts");
155
- const classesOpenApi = path.join(classesDir, "index.ts");
156
-
157
- // Очищаем папку, если нужно
158
- if (config.options?.cleanOutputDir !== false) {
159
- cleanGeneratedFolder(outputDir);
160
- }
161
-
162
- // Обновляем API-документацию
163
- if (config.apiDocsUrl) {
164
- await updateApiDocsJson(config.apiDocsUrl, "api-docs.json");
165
- }
166
-
167
- // Генерация кода на основе спецификаций OpenAPI
168
- const apiDocsPath = config.apiDocsPath || "api-docs.json";
169
- await parseAndGenerate(apiDocsPath, config);
170
-
171
- // Генерация конфигурации axios, если нужно
172
- if (config.options?.generateAxiosConfig) {
173
- generateAxiosConfig(config, outputDir);
174
- }
175
-
176
- // Генерация файлов index.ts для интерфейсов
177
- if (config.options?.generateIndexFiles !== false) {
178
- await generateIndexFileWithOpenApi(interfacesDir, interfacesOpenApi);
179
- await generateIndexFileWithOpenApi(classesDir, classesOpenApi);
180
- await generateMainIndexFile(outputDir);
181
- }
182
-
183
- console.log("Generation completed successfully.");
184
- }
185
-
186
- // Вызов
187
- main().catch((err) => console.error("Error during generation:", err));
@@ -1,19 +0,0 @@
1
- /**
2
- * Маппинг типов OpenAPI в TypeScript.
3
- * @param {string} openapiType - Тип из спецификации OpenAPI.
4
- * @returns {string} - Соответствующий тип TypeScript.
5
- */
6
- function mapType(openapiType) {
7
- const typeMapping = {
8
- string: "string",
9
- integer: "number",
10
- boolean: "boolean",
11
- array: "any[]",
12
- object: "Record<string, any>",
13
- binary: "File",
14
- };
15
-
16
- return typeMapping[openapiType] || "any";
17
- }
18
-
19
- module.exports = {mapType};