@paulpugovkin/api-docs-axios-ts-generator 1.0.5

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.
@@ -0,0 +1,45 @@
1
+ const fs = require("fs");
2
+ const path = require("path");
3
+
4
+ /**
5
+ * Генерирует файл `index.ts`, экспортирующий все модули из указанной папки.
6
+ * @param {string} targetDir - Папка, для которой нужно сгенерировать экспорт.
7
+ * @param {string} outputFile - Полный путь для записи файла `index.ts`.
8
+ */
9
+ function generateIndexFileWithOpenApi(targetDir, outputFile) {
10
+ const exports = [];
11
+
12
+ // Рекурсивно перебираем все файлы в целевой директории
13
+ function walkDir(currentDir) {
14
+ const entries = fs.readdirSync(currentDir, {withFileTypes: true});
15
+
16
+ for (const entry of entries) {
17
+ const fullPath = path.join(currentDir, entry.name);
18
+
19
+ if (entry.isDirectory()) {
20
+ // Рекурсивный вызов для папок
21
+ walkDir(fullPath);
22
+ } else if (
23
+ entry.isFile() &&
24
+ entry.name.endsWith(".ts") &&
25
+ entry.name !== "index.ts"
26
+ ) {
27
+ // Вычисляем относительный путь
28
+ const relativePath = path
29
+ .relative(targetDir, fullPath)
30
+ .replace(/\\/g, "/");
31
+ const modulePath = relativePath.replace(/\.ts$/, "");
32
+ exports.push(`export * from './${modulePath}';`);
33
+ }
34
+ }
35
+ }
36
+
37
+ walkDir(targetDir); // Запуск обхода с целевой директории
38
+
39
+ // Записываем все экспорты в файл index.ts
40
+ fs.writeFileSync(outputFile, exports.join("\n"), {encoding: "utf-8"});
41
+
42
+ console.log(`Generated ${outputFile} with ${exports.length} exports.`);
43
+ }
44
+
45
+ module.exports = {generateIndexFileWithOpenApi};
@@ -0,0 +1,75 @@
1
+ const {resolveType} = require("../resolve-type/resolveType"); // Импорт функции resolveType
2
+
3
+ function capitalizeOnlyFirst(text) {
4
+ return text ? text.charAt(0).toUpperCase() + text.slice(1) : text;
5
+ }
6
+
7
+ function mergeAllOf(allOf, schemaRefs, usedInterfaces) {
8
+ let mergedProperties = {};
9
+ let extendsInterfaces = [];
10
+
11
+ for (const item of allOf) {
12
+ if (item.$ref) {
13
+ const refName = item.$ref.split("/").pop();
14
+ usedInterfaces.add(refName);
15
+ extendsInterfaces.push(refName);
16
+ } else if (item.properties) {
17
+ mergedProperties = {...mergedProperties, ...item.properties};
18
+ }
19
+ }
20
+
21
+ return {mergedProperties, extendsInterfaces};
22
+ }
23
+
24
+ function processOneOf(oneOf, schemaRefs, usedInterfaces) {
25
+ const unionTypes = [];
26
+
27
+ for (const item of oneOf) {
28
+ if (item.$ref) {
29
+ const refName = item.$ref.split("/").pop();
30
+ usedInterfaces.add(refName);
31
+ unionTypes.push(refName);
32
+ }
33
+ }
34
+
35
+ return unionTypes;
36
+ }
37
+
38
+ function generateInterface(name, schema, schemaRefs) {
39
+ const usedInterfaces = new Set();
40
+ let properties = schema.properties || {};
41
+ let extendsInterfaces = [];
42
+
43
+ // Обработка allOf
44
+ if (schema.allOf) {
45
+ const merged = mergeAllOf(schema.allOf, schemaRefs, usedInterfaces);
46
+ properties = {...properties, ...merged.mergedProperties};
47
+ extendsInterfaces = merged.extendsInterfaces;
48
+ }
49
+
50
+ // Обработка oneOf
51
+ if (schema.oneOf) {
52
+ const unionTypes = processOneOf(schema.oneOf, schemaRefs, usedInterfaces);
53
+ extendsInterfaces.push(...unionTypes);
54
+ }
55
+
56
+ const props = [];
57
+
58
+ for (const [propName, propDetails] of Object.entries(properties)) {
59
+ const tsType = resolveType(propDetails, schemaRefs, usedInterfaces);
60
+ props.push(` ${propName}: ${tsType};`);
61
+ }
62
+
63
+ const imports = Array.from(usedInterfaces)
64
+ .sort()
65
+ .map((interfaceName) => `import { ${interfaceName} } from './';`)
66
+ .join("\n");
67
+
68
+ const extendsClause = extendsInterfaces.length
69
+ ? ` extends ${extendsInterfaces.join(", ")}`
70
+ : "";
71
+
72
+ return `${imports}\n\nexport interface ${capitalizeOnlyFirst(name)}${extendsClause} {\n${props.join("\n")}\n}\n`;
73
+ }
74
+
75
+ module.exports = {generateInterface};
@@ -0,0 +1,136 @@
1
+ const {resolveType} = require("../resolve-type/resolveType");
2
+
3
+ /**
4
+ * Функция для генерации JSDoc на основе массива объектов
5
+ * @param {object} method - объект метода
6
+ * @returns {string} - JSDoc строка
7
+ */
8
+ function generateJSDoc(method) {
9
+
10
+ let valueArg = ""
11
+
12
+ let formDataArg = ""
13
+
14
+ const params = method.parameters || [];
15
+
16
+ const summary = method.summary || "";
17
+
18
+ const docLines = [`/**`];
19
+
20
+ if (method.requestBody) {
21
+ const content = method.requestBody.content || {};
22
+ if (content["application/json"]) {
23
+ const schema = content["application/json"].schema || {};
24
+ if (schema.oneOf) {
25
+ valueArg = schema.oneOf.map(s => s.$ref.split("/").pop()).join(' | ');
26
+ } else if (schema.$ref) {
27
+ const refName = schema.$ref.split("/").pop();
28
+ if (!!refName) {
29
+ valueArg = refName
30
+ }
31
+ }
32
+ }
33
+ if (content["multipart/form-data"]) {
34
+ formDataArg = 'FormData'
35
+ }
36
+ }
37
+
38
+ if (!!summary) {
39
+ docLines.push(` * @description ${summary}`);
40
+ }
41
+
42
+ if (!!formDataArg) {
43
+ docLines.push(` * @param {${formDataArg}} data - тело запроса`)
44
+ }
45
+
46
+ if (!!valueArg) {
47
+ docLines.push(` * @param {${valueArg}} values - тело запроса`)
48
+ }
49
+
50
+ const queryParams = {}; // Хранилище для параметров с in: 'query'
51
+ const otherParams = []; // Хранилище для всех остальных параметров
52
+
53
+ // Разделение параметров на query и остальные
54
+ params.forEach((param) => {
55
+ if (param.in === "query") {
56
+ if (param.name.includes(".")) {
57
+ const [objectName, propertyName] = param.name.split(".");
58
+ if (!queryParams[objectName]) {
59
+ queryParams[objectName] = [];
60
+ }
61
+ queryParams[objectName].push({
62
+ propertyName,
63
+ description: param.description || "",
64
+ type: param.schema.type || "string",
65
+ required: param.required || false,
66
+ });
67
+ } else {
68
+ if (!queryParams[param.name]) {
69
+ queryParams[param.name] = [];
70
+ }
71
+ queryParams[param.name].push({
72
+ description: param.description || "",
73
+ type: param.schema.type || "string",
74
+ required: param.required || false,
75
+ });
76
+ }
77
+ } else {
78
+ otherParams.push({
79
+ name: param.name,
80
+ description: param.description || "",
81
+ type: param.schema.type || "string",
82
+ required: param.required || false,
83
+ });
84
+ }
85
+ });
86
+
87
+ // Добавляем queryParams в JSDoc
88
+ if (Object.keys(queryParams).length > 0) {
89
+ docLines.push(` * @param {object} queryParams`);
90
+ Object.keys(queryParams).forEach((key) => {
91
+ const group = queryParams[key];
92
+ // Сортировка: сначала обязательные параметры
93
+ const sortedGroup = group.sort(
94
+ (a, b) => (b.required ? 1 : 0) - (a.required ? 1 : 0)
95
+ );
96
+ if (
97
+ sortedGroup.length === 1 &&
98
+ sortedGroup[0].propertyName === undefined
99
+ ) {
100
+ // Одиночный параметр без вложенности
101
+ const param = sortedGroup[0];
102
+ const jsType = param.type === "integer" ? "number" : param.type;
103
+ docLines.push(
104
+ ` * @param {${jsType}} queryParams.${key} - ${param.description}`
105
+ );
106
+ } else {
107
+ // Вложенные параметры
108
+ docLines.push(
109
+ ` * @param {object} queryParams.${key} - Query parameter`
110
+ );
111
+ sortedGroup.forEach((subParam) => {
112
+ const {propertyName, description, type} = subParam;
113
+ const jsType = type === "integer" ? "number" : type;
114
+ docLines.push(
115
+ ` * @param {${jsType}} queryParams.${key}.${propertyName} - ${description}`
116
+ );
117
+ });
118
+ }
119
+ });
120
+ }
121
+
122
+ // Сортировка других параметров: сначала обязательные
123
+ const sortedOtherParams = otherParams.sort(
124
+ (a, b) => (b.required ? 1 : 0) - (a.required ? 1 : 0)
125
+ );
126
+ sortedOtherParams.forEach((param) => {
127
+ const jsType = param.type === "integer" ? "number" : param.type;
128
+ docLines.push(` * @param {${jsType}} ${param.name} - ${param.description}`);
129
+ });
130
+
131
+ docLines.push(` * @param {AxiosRequestConfig} config - Конфигурация axios`);
132
+ docLines.push(` */`);
133
+ return docLines.join("\n");
134
+ }
135
+
136
+ module.exports = {generateJSDoc};
@@ -0,0 +1,21 @@
1
+ const fs = require("fs");
2
+ const path = require("path");
3
+
4
+ /**
5
+ * Генерирует главный `index.ts` файл в указанной папке, экспортирующий `openapi.ts` из интерфейсов и классов.
6
+ * @param {string} outputDir - Корневая папка, где будет создан `index.ts`.
7
+ */
8
+ function generateMainIndexFile(outputDir) {
9
+ const indexFilePath = path.join(outputDir, "index.ts");
10
+ const exports = [
11
+ "export * from './interfaces/';",
12
+ "export * from './classes/';",
13
+ ];
14
+
15
+ // Создание или перезапись index.ts
16
+ fs.writeFileSync(indexFilePath, exports.join("\n"), {encoding: "utf-8"});
17
+
18
+ console.log(`Generated main index.ts at ${indexFilePath}.`);
19
+ }
20
+
21
+ module.exports = {generateMainIndexFile};
@@ -0,0 +1,264 @@
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};
@@ -0,0 +1,79 @@
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 };