swagger-fsd-gen 1.0.3 → 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.
package/CHANGELOG.md CHANGED
@@ -1,5 +1,24 @@
1
1
  # Changelog
2
2
 
3
+ ## 1.0.5
4
+
5
+ - Rebased behavior to the 1.0.2 line.
6
+ - Fixed only one issue in TanStack mutation key generation:
7
+ - when API path segments contain `-`, generated mutation key identifiers now use `_`.
8
+ - example: `undo-upload` -> `UNDO_UPLOAD`
9
+ - No stale-file prune behavior.
10
+ - No generated folder/template structure changes.
11
+
12
+ ## 1.0.4
13
+
14
+ - Added stale generated module pruning (enabled by default) so removed/renamed Swagger tags no longer leave old `entities/{module}/api/*` files behind.
15
+ - Added `--prune-stale <true|false>` option to control stale-file pruning behavior.
16
+ - Fixed axios template method generation to use correct axios signatures:
17
+ - `post/put/patch(url, data, config)`
18
+ - `delete(url, config)` with optional `data` in config
19
+ - `get(url, config)` and other non-body methods.
20
+ - This resolves mixed-client leftovers (for example stale `ky` modules) when regenerating with `--http-client axios`.
21
+
3
22
  ## 1.0.3
4
23
 
5
24
  - Fixed invalid TypeScript identifiers in generated mutation keys when API paths contain `-` (for example `undo-upload`, `applied-campaigns`, `notification-settings`, `custom-filters`).
package/README.md CHANGED
@@ -1,10 +1,10 @@
1
1
  # swagger-fsd-gen
2
2
 
3
- Swagger/OpenAPI 문서를 기반으로 **axios/ky + TanStack Query + FSD(Feature-Sliced Design) 패턴**에 맞는 API 클라이언트를 자동으로 생성하는 도구입니다.
3
+ Swagger/OpenAPI 문서를 기반으로 **ky + TanStack Query + FSD(Feature-Sliced Design) 패턴**에 맞는 API 클라이언트를 자동으로 생성하는 도구입니다.
4
4
 
5
5
  ## ✨ 주요 기능
6
6
 
7
- - 🚀 **axios/ky HTTP 클라이언트 선택** 기반 API 클래스 자동 생성
7
+ - 🚀 **ky HTTP 클라이언트** 기반 API 클래스 자동 생성
8
8
  - 🔄 **TanStack Query** 훅 자동 생성 (useQuery, useMutation)
9
9
  - 📁 **FSD(Feature-Sliced Design)** 패턴 자동 적용
10
10
  - 🔐 **HTTP Basic Authentication** 지원
@@ -33,12 +33,6 @@ npx generate-all --uri https://api.example.com/swagger.json --username your-user
33
33
  # yarn
34
34
  yarn fetch-swagger --url https://api.example.com/swagger.json --username your-username --password your-password
35
35
  yarn generate-all --uri https://api.example.com/swagger.json --username your-username --password your-password
36
-
37
- # axios 클라이언트로 생성
38
- yarn generate-all --uri https://api.example.com/swagger.json --http-client axios
39
-
40
- # ky 클라이언트로 생성 (기본값)
41
- yarn generate-all --uri https://api.example.com/swagger.json --http-client ky
42
36
  ```
43
37
 
44
38
  ### 2. package.json에 스크립트 추가 (권장)
@@ -97,7 +91,6 @@ src/
97
91
  | --uri | Swagger 문서 URL/경로 | 필수 |
98
92
  | --username | Basic Auth 사용자명 | - |
99
93
  | --password | Basic Auth 비밀번호 | - |
100
- | --http-client | HTTP 클라이언트 선택 | ky (`axios`, `ky`) |
101
94
  | --dto-output-path | DTO 파일 경로 | src/shared/api/dto.ts |
102
95
  | --api-output-path | API 클래스 경로 | src/entities/{moduleName}/api/index.ts |
103
96
  | --query-output-path | Query 훅 경로 | src/entities/{moduleName}/api/queries.ts |
package/USAGE.md CHANGED
@@ -40,12 +40,6 @@ generate-all --uri https://api.example.com/swagger.json
40
40
  # 로컬 파일에서 생성
41
41
  generate-all --uri ./swagger/my-api.yml
42
42
 
43
- # axios 클라이언트로 생성
44
- generate-all --uri ./swagger/my-api.yml --http-client axios
45
-
46
- # ky 클라이언트로 생성 (기본값)
47
- generate-all --uri ./swagger/my-api.yml --http-client ky
48
-
49
43
  # 인증이 필요한 경우
50
44
  generate-all --uri https://api.example.com/swagger.json --username admin --password secret
51
45
  ```
@@ -71,11 +65,7 @@ src/
71
65
  ### 1. 필요한 패키지 설치
72
66
 
73
67
  ```bash
74
- # ky를 사용할 때
75
68
  npm install ky @tanstack/react-query
76
-
77
- # axios를 사용할 때
78
- npm install axios @tanstack/react-query
79
69
  ```
80
70
 
81
71
  ### 2. API 인스턴스 설정
package/package.json CHANGED
@@ -1,8 +1,8 @@
1
1
  {
2
2
  "name": "swagger-fsd-gen",
3
- "version": "1.0.3",
3
+ "version": "1.0.5",
4
4
  "main": "index.js",
5
- "description": "Swagger API client generator that creates type-safe API clients using axios or ky and TanStack Query with Feature Sliced Design pattern. Automatically generates API client code from Swagger/OpenAPI specifications.",
5
+ "description": "Swagger API client generator that creates type-safe API clients using ky and TanStack Query with Feature Sliced Design pattern. Automatically generates API client code from Swagger/OpenAPI specifications.",
6
6
  "scripts": {
7
7
  "test": "echo \"Error: no test specified\" && exit 1"
8
8
  },
@@ -12,7 +12,6 @@
12
12
  "api-client",
13
13
  "typescript",
14
14
  "ky",
15
- "axios",
16
15
  "tanstack-query",
17
16
  "code-generator",
18
17
  "fsd"
@@ -2,7 +2,7 @@
2
2
 
3
3
  /**
4
4
  * Swagger API 클라이언트 자동 생성 도구
5
- * - axios/ky HTTP 클라이언트 기반 API 클래스 생성
5
+ * - ky HTTP 클라이언트 기반 API 클래스 생성
6
6
  * - TanStack Query 훅 생성 (useQuery, useMutation)
7
7
  * - FSD(Feature-Sliced Design) 패턴 적용
8
8
  */
@@ -72,7 +72,6 @@ const parseArguments = () => {
72
72
  "uri",
73
73
  "username",
74
74
  "password",
75
- "http-client",
76
75
  "dto-output-path",
77
76
  "api-output-path",
78
77
  "api-instance-output-path",
@@ -84,7 +83,6 @@ const parseArguments = () => {
84
83
  u: "uri",
85
84
  un: "username",
86
85
  pw: "password",
87
- hc: "http-client",
88
86
  dp: "dto-output-path",
89
87
  ap: "api-output-path",
90
88
  aip: "api-instance-output-path",
@@ -98,7 +96,6 @@ const parseArguments = () => {
98
96
  uri: argv.uri,
99
97
  username: argv.username,
100
98
  password: argv.password,
101
- httpClient: argv["http-client"],
102
99
  dtoOutputPath: argv["dto-output-path"],
103
100
  apiOutputPath: argv["api-output-path"],
104
101
  apiInstanceOutputPath: argv["api-instance-output-path"],
@@ -108,23 +105,6 @@ const parseArguments = () => {
108
105
  };
109
106
  };
110
107
 
111
- /**
112
- * HTTP 클라이언트 모드 정규화
113
- * @param {string | undefined} input
114
- * @returns {"axios" | "ky"}
115
- */
116
- const resolveHttpClient = (input) => {
117
- const normalized = (input ?? "ky").toString().toLowerCase();
118
-
119
- if (normalized === "axios" || normalized === "ky") {
120
- return normalized;
121
- }
122
-
123
- throw new Error(
124
- `Invalid --http-client value "${input}". Supported values: axios | ky`
125
- );
126
- };
127
-
128
108
  /**
129
109
  * 출력 경로 설정 (FSD 패턴 기본값)
130
110
  * @param {Object} args - 명령행 인수
@@ -192,7 +172,6 @@ const printUsage = (outputPaths) => {
192
172
  console.error(
193
173
  "Usage: generate-all --uri <swagger-url|swagger-file-name> " +
194
174
  "[--username <username>] [--password <password>] " +
195
- "[--http-client <axios|ky>] " +
196
175
  "[--dto-output-path <dto-output-path>] " +
197
176
  "[--api-output-path <api-output-path>] " +
198
177
  "[--query-output-path <query-output-path>] " +
@@ -217,12 +196,10 @@ export const generateApiCode = async ({
217
196
  uri,
218
197
  username,
219
198
  password,
220
- httpClient,
221
199
  templates,
222
200
  ...params
223
201
  }) => {
224
202
  const isLocal = !isUrl(uri);
225
- const httpClientType = httpClient === "axios" ? "axios" : "fetch";
226
203
 
227
204
  return generateApi({
228
205
  input: isLocal ? path.resolve(process.cwd(), uri) : undefined,
@@ -249,7 +226,6 @@ export const generateApiCode = async ({
249
226
  schemaParsers: {
250
227
  complexAnyOf: AnyOfSchemaParser,
251
228
  },
252
- httpClientType,
253
229
  ...params,
254
230
  });
255
231
  };
@@ -272,7 +248,7 @@ const formatWithProjectPrettier = (filePath) => {
272
248
  * @param {Object} outputPaths - 출력 경로 설정
273
249
  */
274
250
  const generateApiFunctionCode = async (args, outputPaths) => {
275
- const { projectTemplate, uri, username, password, httpClient } = args;
251
+ const { projectTemplate, uri, username, password } = args;
276
252
 
277
253
  const templatePath = projectTemplate
278
254
  ? path.resolve(process.cwd(), projectTemplate)
@@ -284,10 +260,8 @@ const generateApiFunctionCode = async (args, outputPaths) => {
284
260
  uri,
285
261
  username,
286
262
  password,
287
- httpClient,
288
263
  templates: templatePath,
289
- // swagger-typescript-api + prettier 조합에서 parser 추론 실패를 방지
290
- prettier: { parser: "typescript" },
264
+ prettier: false, // prettier 비활성화
291
265
  });
292
266
 
293
267
  for (const { fileName, fileContent } of apiFunctionCode.files) {
@@ -333,7 +307,7 @@ const generateApiFunctionCode = async (args, outputPaths) => {
333
307
  * @param {Object} outputPaths - 출력 경로 설정
334
308
  */
335
309
  const generateTanstackQueryCode = async (args, outputPaths) => {
336
- const { projectTemplate, uri, username, password, httpClient } = args;
310
+ const { projectTemplate, uri, username, password } = args;
337
311
 
338
312
  const templatePath = projectTemplate
339
313
  ? path.resolve(process.cwd(), projectTemplate, "tanstack-query")
@@ -345,10 +319,8 @@ const generateTanstackQueryCode = async (args, outputPaths) => {
345
319
  uri,
346
320
  username,
347
321
  password,
348
- httpClient,
349
322
  templates: templatePath,
350
- // swagger-typescript-api + prettier 조합에서 parser 추론 실패를 방지
351
- prettier: { parser: "typescript" },
323
+ prettier: false, // prettier 비활성화
352
324
  });
353
325
 
354
326
  for (const { fileName, fileContent } of tanstackQueryCode.files) {
@@ -397,9 +369,6 @@ const main = async () => {
397
369
  }
398
370
 
399
371
  try {
400
- args.httpClient = resolveHttpClient(args.httpClient);
401
- console.log(`🌐 HTTP client mode: ${args.httpClient}`);
402
-
403
372
  // 1. API 클래스와 DTO 생성
404
373
  await generateApiFunctionCode(args, outputPaths);
405
374
 
package/templates/api.ejs CHANGED
@@ -2,14 +2,13 @@
2
2
  /**
3
3
  * API 클래스 생성 템플릿
4
4
  * - Swagger 태그별로 API 클래스 생성
5
- * - axios/ky HTTP 클라이언트 기반
5
+ * - ky HTTP 클라이언트 기반
6
6
  * - 타입 안전성을 위한 TypeScript 지원
7
7
  * - 쿼리 파라미터 처리를 위한 유틸리티 메서드 포함
8
8
  */
9
9
 
10
10
  const { utils, route, config, modelTypes } = it;
11
11
  const { _, pascalCase, require } = utils;
12
- const isAxios = config.httpClientType === 'axios';
13
12
 
14
13
  // 모듈명을 PascalCase로 변환하여 클래스명 생성 (예: user -> UserApi)
15
14
  const apiClassName = pascalCase(route.moduleName);
@@ -21,32 +20,27 @@ const routes = route.routes;
21
20
  const dataContracts = _.map(modelTypes, "name");
22
21
  %>
23
22
 
24
- <% if (isAxios) { %>
25
- import type { AxiosInstance, AxiosRequestConfig } from 'axios';
26
- <% } else { %>
27
- import type { KyInstance, Options } from 'ky';
28
- <% } %>
23
+ import { KyInstance, Options } from 'ky';
29
24
 
30
25
  <%~ includeFile('./dto-import.ejs', { ...it }) %>
31
26
 
32
27
  /**
33
28
  * <%= route.moduleName %> 모듈 API 클래스
34
- * - 선택된 HTTP 클라이언트를 사용한 API 호출
29
+ * - ky HTTP 클라이언트를 사용한 API 호출
35
30
  * - 타입 안전성을 위한 TypeScript 지원
36
31
  * - 쿼리 파라미터 자동 변환 기능
37
32
  */
38
33
  export class <%= apiClassName %>Api {
39
- private readonly instance: <%= isAxios ? 'AxiosInstance' : 'KyInstance' %>;
34
+ private readonly instance: KyInstance;
40
35
 
41
36
  /**
42
37
  * API 클래스 생성자
43
- * @param instance - HTTP 클라이언트 인스턴스
38
+ * @param instance - ky HTTP 클라이언트 인스턴스
44
39
  */
45
- constructor(instance: <%= isAxios ? 'AxiosInstance' : 'KyInstance' %>) {
40
+ constructor(instance: KyInstance) {
46
41
  this.instance = instance;
47
42
  }
48
43
 
49
- <% if (!isAxios) { %>
50
44
  /**
51
45
  * 객체를 URLSearchParams로 변환하는 정적 유틸리티 메서드
52
46
  * - 중첩된 객체나 배열을 쿼리 파라미터로 변환
@@ -76,7 +70,6 @@ static createSearchParams(
76
70
 
77
71
  return urlSearchParams;
78
72
  }
79
- <% } %>
80
73
 
81
74
  <% for (const route of routes) { %>
82
75
  <%~ includeFile('./procedure-call.ejs', { ...it, route, apiClassName }) %>
@@ -10,7 +10,6 @@
10
10
  const { utils, route, config, apiClassName } = it;
11
11
  const { _, pascalCase } = utils;
12
12
  const routeDocs = includeFile("./route-docs", { config, route, utils });
13
- const isAxios = config.httpClientType === 'axios';
14
13
 
15
14
  /**
16
15
  * API 메서드명 생성
@@ -43,7 +42,6 @@ const headerName = headers ? `${pascalCase(route.routeName.usage)}Headers` : nul
43
42
 
44
43
  // 쿼리 파라미터 타입명 생성
45
44
  const queryTypeName = query ? `${pascalCase(route.routeName.usage)}QueryParams` : null;
46
- const instanceParamName = isAxios ? 'axiosInstance' : 'kyInstance';
47
45
 
48
46
  /**
49
47
  * 메서드 파라미터 문자열 생성
@@ -63,21 +61,20 @@ const generateParams = (params, query, payload, headerName) => {
63
61
  ...(payload ? [`data${payload.optional ? '?' : ''}: ${payload.type}`] : [])
64
62
  ];
65
63
 
66
- // HTTP client options에서 제외할 타입들 (중복 방지)
64
+ // ky Options에서 제외할 타입들 (중복 방지)
67
65
  const omitTypes = [
68
- ...(query ? [isAxios ? "'params'" : "'searchParams'"] : []),
69
- ...(payload ? [isAxios ? "'data'" : "'json'"] : []),
66
+ ...(query ? ["'searchParams'"] : []),
67
+ ...(payload ? ["'json'"] : []),
70
68
  ...(headerName ? ["'headers'"] : [])
71
69
  ];
72
70
 
73
- // HTTP client options 타입 생성 (중복되는 속성 제외)
74
- const optionsBaseType = isAxios ? 'AxiosRequestConfig' : 'Options';
71
+ // ky Options 타입 생성 (중복되는 속성 제외)
75
72
  const optionsType = omitTypes.length > 0
76
- ? `Omit<${optionsBaseType}, ${omitTypes.join(' | ')}>${headerName ? ` & { headers: ${headerName} }` : ''}`
77
- : optionsBaseType;
73
+ ? `Omit<Options, ${omitTypes.join(' | ')}>${headerName ? ` & { headers: ${headerName} }` : ''}`
74
+ : 'Options';
78
75
 
79
- // HTTP 클라이언트 인스턴스와 옵션 파라미터 추가
80
- paramList.push(`${instanceParamName}?: ${isAxios ? 'AxiosInstance' : 'KyInstance'}`, `options?: ${optionsType}`);
76
+ // ky 인스턴스와 옵션 파라미터 추가
77
+ paramList.push('kyInstance?: KyInstance', `options?: ${optionsType}`);
81
78
  return paramList.join(', ');
82
79
  };
83
80
  %>
@@ -85,25 +82,14 @@ const generateParams = (params, query, payload, headerName) => {
85
82
  /**
86
83
  <%~ routeDocs.lines %> */
87
84
  <%= functionName %>(<%~ generateParams(pathParams, query, payload, headerName) %>) {
88
- // HTTP 클라이언트 인스턴스 선택 (전달된 인스턴스 또는 기본 인스턴스)
89
- const instance = <%= instanceParamName %> ?? this.instance;
85
+ // ky 인스턴스 선택 (전달된 인스턴스 또는 기본 인스턴스)
86
+ const instance = kyInstance ?? this.instance;
90
87
 
91
- <% if (query && !isAxios) { %>
88
+ <% if (query) { %>
92
89
  // 쿼리 파라미터를 URLSearchParams로 변환
93
90
  const urlSearchParams = <%= apiClassName %>Api.createSearchParams(params);
94
91
  <% } %>
95
92
 
96
- <% if (isAxios) { %>
97
- return instance.<%= route.request.method.toLowerCase() %><<%= responseType %>>(`<%= route.request.path.replace(/{/g, '{').replace(/}/g, '}').slice(1,) %>`, {
98
- <% if (query) { %>
99
- params, // 쿼리 파라미터 설정
100
- <% } %>
101
- <% if (payload) { %>
102
- data, // 요청 본문 설정
103
- <% } %>
104
- ...options, // 추가 axios 옵션 병합
105
- }).then((response) => response.data);
106
- <% } else { %>
107
93
  // HTTP 요청 실행 및 JSON 응답 반환
108
94
  return instance.<%= route.request.method.toLowerCase() %><<%= responseType %>>(`<%= route.request.path.replace(/{/g, '{').replace(/}/g, '}').slice(1,) %>`,{
109
95
  <% if (query) { %>
@@ -114,5 +100,4 @@ return instance.<%= route.request.method.toLowerCase() %><<%= responseType %>>(`
114
100
  <% } %>
115
101
  ...options, // 추가 ky 옵션 병합
116
102
  }).json();
117
- <% } %>
118
103
  }
@@ -4,13 +4,12 @@ const { _, pascalCase, require } = utils;
4
4
  const apiClassName = pascalCase(route.moduleName);
5
5
  const routes = route.routes;
6
6
  const dataContracts = _.map(modelTypes, "name");
7
- const isAxios = config.httpClientType === 'axios';
8
7
  %>
9
8
 
10
- import { <%= isAxios ? 'axiosInstance' : 'kyInstance' %> } from "@/shared/api"
9
+ import { kyInstance } from "@/shared/api"
11
10
 
12
11
  import { <%= apiClassName %>Api } from './index'
13
12
 
14
- const <%= route.moduleName %>Api = new <%= apiClassName %>Api(<%= isAxios ? 'axiosInstance' : 'kyInstance' %>);
13
+ const <%= route.moduleName %>Api = new <%= apiClassName %>Api(kyInstance);
15
14
 
16
15
  export { <%= route.moduleName %>Api };
@@ -6,10 +6,6 @@
6
6
  const { utils, route, modelTypes, config } = it;
7
7
  const { pascalCase, internalCase: camelCase } = utils;
8
8
  const { moduleName, routes } = route;
9
- const isAxios = config.httpClientType === 'axios';
10
- const clientInstanceParam = isAxios ? 'axiosInstance' : 'kyInstance';
11
- const clientInstanceType = isAxios ? 'AxiosInstance' : 'KyInstance';
12
- const clientOptionsType = isAxios ? 'AxiosRequestConfig' : 'Options';
13
9
 
14
10
  const getFunctionName = ({ request: { method, path } }) =>
15
11
  `${method}${pascalCase(path
@@ -93,7 +89,7 @@ if (mutationConfigs.length > 0) {
93
89
  .filter(config => config.hasVariables)
94
90
  .map(config => {
95
91
  const headerName = config.route.specificArgs.headers ? `${pascalCase(config.route.routeName.usage)}Headers` : null;
96
- return `type T${pascalCase(config.functionName)}Variables = { ${config.requestParams}, ${clientInstanceParam}?: ${clientInstanceType}; options?: ${headerName ? `Omit<${clientOptionsType}, 'headers'> & { headers: ${headerName} }` : clientOptionsType}; };`
92
+ return `type T${pascalCase(config.functionName)}Variables = { ${config.requestParams}, kyInstance?: KyInstance; options?: ${headerName ? `Omit<Options, 'headers'> & { headers: ${headerName} }` : 'Options'}; };`
97
93
  })
98
94
  .join('\n');
99
95
 
@@ -107,11 +103,7 @@ UseMutationOptions,
107
103
  useQueryClient,
108
104
  } from '@tanstack/react-query';
109
105
  import { useMutation } from '@tanstack/react-query';
110
- <% if (isAxios) { %>
111
- import type{ AxiosInstance, AxiosRequestConfig } from 'axios';
112
- <% } else { %>
113
106
  import type{ KyInstance, Options } from 'ky';
114
- <% } %>
115
107
 
116
108
  <%~ includeFile('../dto-import.ejs', { ...it }) %>
117
109
 
@@ -127,8 +119,8 @@ const mutations = {
127
119
  <% for (const config of mutationConfigs) { %>
128
120
  <%= config.functionName %>: () => ({
129
121
  mutationFn: <% if (config.hasVariables) { %>(variables: T<%= pascalCase(config.functionName) %>Variables) => {
130
- const { <%= config.requestParamsWithoutTypes %>, <%= clientInstanceParam %>, options } = variables;
131
- return <%= moduleName %>Api.<%= config.functionName %>(<%= config.requestParamsWithoutTypes %>, <%= clientInstanceParam %>, options);
122
+ const { <%= config.requestParamsWithoutTypes %>, kyInstance, options } = variables;
123
+ return <%= moduleName %>Api.<%= config.functionName %>(<%= config.requestParamsWithoutTypes %>, kyInstance, options);
132
124
  }<% } else { %>() => <%= moduleName %>Api.<%= config.functionName %>()<% } %>,
133
125
  mutationKey: <% ~moduleName.toUpperCase() %>_MUTATION_KEY.<% ~config.mutationKeyName %>,
134
126
  }),