swagger-fsd-gen 1.0.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.
@@ -0,0 +1,195 @@
1
+ <%
2
+ /**
3
+ * @description Tanstack Query - Query 생성 템플릿 입니다.
4
+ */
5
+
6
+ const { utils, route, modelTypes, config } = it;
7
+ const { pascalCase, internalCase: camelCase, _: lodash } = utils;
8
+ const { moduleName, routes } = route;
9
+
10
+ // 유틸리티 함수들
11
+ const removeBracket = (type) => type.replace(/[()]/g, '');
12
+
13
+ const cleanupType = (type) => {
14
+ return type
15
+ // 배열 타입 주변의 불필요한 괄호 제거
16
+ .replace(/\(([A-Za-z0-9_]+)\)(\[\])/g, '$1$2')
17
+
18
+ // 속성 뒤의 불필요한 공백 제거 (콤마 앞의 공백 제거)
19
+ .replace(/\s+,/g, ',')
20
+
21
+ // 배열 타입 뒤의 콤마와 공백 제거
22
+ .replace(/(\[\]),\s*/g, '$1')
23
+
24
+ // 객체 끝의 불필요한 공백 제거
25
+ .replace(/\s+}/g, '}')
26
+
27
+ // 객체 시작의 불필요한 공백 제거
28
+ .replace(/{\s+/g, '{')
29
+
30
+ // 연속된 공백 제거
31
+ .replace(/\s{2,}/g, ' ');
32
+ };
33
+
34
+ // 경로 관련 함수들
35
+ const getFunctionName = ({ request: { method, path } }) => {
36
+ const segments = path.split('/').map(segment => {
37
+ if (segment.includes('${')) {
38
+ return `By_${segment.replace(/[${}]/g, '')}`;
39
+ }
40
+ return segment;
41
+ });
42
+
43
+ return `${method}${pascalCase(segments.join('_'))}`;
44
+ };
45
+
46
+ const buildQueryKey = ({ path }) => {
47
+ return path
48
+ .split('/')
49
+ .filter(segment => segment && segment !== 'api')
50
+ .map(segment => {
51
+ if (segment.match(/\${/)) {
52
+ return segment.replace(/[${}]/g, '').toUpperCase().replace(/-/g, '_');
53
+ }
54
+ return segment.toUpperCase().replace(/-/g, '_');
55
+ })
56
+ .join('_');
57
+ };
58
+
59
+ const buildPathQueryKey = ({ path }) => {
60
+ return path
61
+ .split('/')
62
+ .filter(segment => segment && segment !== 'api')
63
+ .map(segment => {
64
+ if (segment.match(/\${/)) {
65
+ return segment.replace(/[${}]/g, '').replace(/-/g, '_');
66
+ }
67
+ return `'${segment.replace(/-/g, '_')}'`;
68
+ });
69
+ };
70
+
71
+ // 요청 파라미터 생성 함수
72
+ const getRequestParams = ({ parameters = [], query, payload, routeName }) => {
73
+ const queryParamsDto = query ? `${pascalCase(routeName.usage)}QueryParams` : '';
74
+
75
+ const paramsWithTypes = lodash.compact([
76
+ ...parameters.map(({ name, type, optional }) =>
77
+ `${name}${optional ? '?' : ''}: ${removeBracket(type)}`
78
+ ),
79
+ queryParamsDto ? `params${query.optional ? '?' : ''}: ${queryParamsDto}` : '',
80
+ payload ? `body${payload.optional ? '?' : ''}: ${removeBracket(payload.type)}` : '',
81
+ ]).join(', ');
82
+
83
+ const paramsWithoutTypes = lodash.compact([
84
+ ...parameters.map(({ name, optional }) => `${name}${optional ? '?' : ''}`),
85
+ query ? 'params' : '',
86
+ payload ? 'body' : '',
87
+ ]).join(', ');
88
+
89
+ return {
90
+ withTypes: paramsWithTypes,
91
+ withoutTypes: paramsWithoutTypes
92
+ };
93
+ };
94
+
95
+ // 쿼리 설정 생성
96
+ const queryConfigs = routes
97
+ .filter(({ request: { method } }) => method === 'get')
98
+ .map(route => {
99
+ const functionName = getFunctionName(route);
100
+ const { request, response } = route;
101
+ const queryKeyName = buildQueryKey(request);
102
+ const pathQueryKey = buildPathQueryKey(request);
103
+
104
+ const { withTypes: requestParams, withoutTypes: requestParamsWithoutTypes } =
105
+ getRequestParams({
106
+ ...request,
107
+ routeName: route.routeName
108
+ });
109
+
110
+ const queryKeyParams = [
111
+ ...pathQueryKey,
112
+ request.query ? 'params' : null,
113
+ request.payload ? 'data' : null,
114
+ ].filter(Boolean).join(', ');
115
+
116
+ return {
117
+ moduleName,
118
+ route,
119
+ functionName: camelCase(functionName),
120
+ hookName: camelCase(`use${pascalCase(functionName)}Query`),
121
+ suspenseHookName: camelCase(`use${pascalCase(functionName)}SuspenseQuery`),
122
+ responseDto: cleanupType(response.type),
123
+ requestParams,
124
+ requestParamsWithoutTypes,
125
+ queryKeyName: `GET_${queryKeyName}`,
126
+ queryKey: `(${requestParams}) => [${queryKeyParams}]`,
127
+ };
128
+ });
129
+ %>
130
+
131
+ <% if (queryConfigs?.length) { %>
132
+ import type {
133
+ DefaultError,
134
+ UseQueryOptions,
135
+ UseSuspenseQueryOptions,
136
+ } from '@tanstack/react-query';
137
+ import { useQuery, useSuspenseQuery } from '@tanstack/react-query';
138
+
139
+ <%~ includeFile('../dto-import.ejs', { ...it }) %>
140
+
141
+ import { <%~ moduleName %>Api } from './instance';
142
+
143
+ export const <% ~moduleName.toUpperCase() %>_QUERY_KEY = {
144
+ <% for (const queryConfig of queryConfigs) { %>
145
+ <% ~queryConfig.queryKeyName %>: <% ~queryConfig.queryKey %>,
146
+ <% } %>
147
+ }
148
+
149
+ const queries = {
150
+ <% for (const queryConfig of queryConfigs) { %>
151
+ <% ~queryConfig.functionName %>: (<% ~queryConfig.requestParams %>) => ({
152
+ queryKey: <% ~moduleName.toUpperCase() %>_QUERY_KEY.<% ~queryConfig.queryKeyName %>(<% ~queryConfig.requestParamsWithoutTypes %>),
153
+ queryFn: () => <% ~moduleName %>Api.<% ~queryConfig.functionName %>(<% ~queryConfig.requestParamsWithoutTypes %>)
154
+ }),
155
+ <% } %>
156
+ }
157
+
158
+ // ---------------------- Query ------------------------------
159
+ <% for (const queryConfig of queryConfigs) {
160
+ const routeDocs = includeFile("../route-docs.ejs", { config, route: queryConfig.route, utils });
161
+ %>
162
+ /**
163
+ <%~ routeDocs.lines %>
164
+ */
165
+ export const <%~ queryConfig.hookName %> = <TData = <%~ queryConfig.responseDto %>>(
166
+ <%~ queryConfig.requestParams ? `${queryConfig.requestParams},`:''%>
167
+ options?: Omit<UseQueryOptions<<%~ queryConfig.responseDto %>, DefaultError, TData>, 'queryKey' | 'queryFn'>
168
+ ) => {
169
+ return useQuery({
170
+ ...queries.<% ~queryConfig.functionName %>(<% ~queryConfig.requestParamsWithoutTypes %>),
171
+ ...options,
172
+ });
173
+ }
174
+
175
+ <% } %>
176
+
177
+ // ------------------ Suspense Query --------------------------
178
+ <% for (const queryConfig of queryConfigs) {
179
+ const routeDocs = includeFile("../route-docs.ejs", { config, route: queryConfig.route, utils });
180
+ %>
181
+ /**
182
+ <%~ routeDocs.lines %>
183
+ */
184
+ export const <%~ queryConfig.suspenseHookName %> = <TData = <%~ queryConfig.responseDto %>>(
185
+ <%~ queryConfig.requestParams ? `${queryConfig.requestParams},`:''%>
186
+ options?: Omit<UseSuspenseQueryOptions<<%~ queryConfig.responseDto %>, DefaultError, TData>, 'queryKey' | 'queryFn'>
187
+ ) => {
188
+ return useSuspenseQuery({
189
+ ...queries.<% ~queryConfig.functionName %>(<% ~queryConfig.requestParamsWithoutTypes %>),
190
+ ...options,
191
+ });
192
+ }
193
+
194
+ <% } %>
195
+ <% } %>
@@ -0,0 +1,152 @@
1
+ <%
2
+ /**
3
+ * @description Tanstack Query - Mutation 생성 템플릿 입니다.
4
+ */
5
+
6
+ const { utils, route, modelTypes, config } = it;
7
+ const { pascalCase, internalCase: camelCase } = utils;
8
+ const { moduleName, routes } = route;
9
+
10
+ const getFunctionName = ({ request: { method, path } }) =>
11
+ `${method}${pascalCase(path
12
+ .split('/')
13
+ .map(segment =>
14
+ segment.includes('${')
15
+ ? `By_${segment.replace(/[${}]/g, '')}`
16
+ : segment
17
+ )
18
+ .join('_'))}`;
19
+
20
+ const removeBracket = (type) => type.replace(/[()]/g, '')
21
+
22
+ const getRequestParams = ({ parameters = [], query, payload, routeName }) => {
23
+ const queryParamsDto = query ? `${pascalCase(routeName.usage)}QueryParams` : '';
24
+
25
+ return {
26
+ withTypes: [
27
+ ...parameters.map(({ name, type, optional }) => `${name}${optional ? '?' : ''}: ${removeBracket(type)}`),
28
+ queryParamsDto ? `params${query.optional ? '?' : ''}: ${queryParamsDto}` : '',
29
+ payload ? `body${payload.optional ? '?' : ''}: ${removeBracket(payload.type)}` : '',
30
+ ].filter(Boolean).join(', '),
31
+ withoutTypes: [
32
+ ...parameters.map(({ name, optional }) => `${name}${optional ? '?' : ''}`),
33
+ query ? `params` : '',
34
+ payload ? 'body' : '',
35
+ ].filter(Boolean).join(', ')
36
+ } };
37
+
38
+ const buildMutationKeyName = ({ path, method }) =>
39
+ method.toUpperCase()+'_'+path
40
+ .split('/')
41
+ .filter(segment => segment )
42
+ .map(segment =>
43
+ segment.match(/\${/)
44
+ ? segment.replace(/[${}]/g, '').toUpperCase()
45
+ : segment.toUpperCase()
46
+ )
47
+ .join('_')
48
+
49
+ const buildPathMutationKey = ({ path }) =>
50
+ path
51
+ .split('/')
52
+ .filter(segment => segment)
53
+ .map(segment =>
54
+ `'${segment.replace(/[${}]/g, '')}'`)
55
+
56
+ const mutationConfigs = routes
57
+ .filter(({ request: { method } }) => method === 'post' || method === 'patch' || method === 'put' || method === 'delete')
58
+ .map(route => {
59
+ const functionName = getFunctionName(route);
60
+ const { request, response } = route;
61
+ const mutationKeyName = buildMutationKeyName(request);
62
+ const mutationKey = buildPathMutationKey(request);
63
+ const { withTypes: requestParams, withoutTypes: requestParamsWithoutTypes } = getRequestParams({
64
+ ...request,
65
+ routeName: route.routeName
66
+ });
67
+
68
+ return {
69
+ moduleName,
70
+ route,
71
+ functionName: camelCase(functionName),
72
+ hookName: `use${pascalCase(functionName)}Mutation`,
73
+ responseDto: removeBracket(response.type),
74
+ hasVariables: requestParams.length > 0,
75
+ requestParams,
76
+ requestParamsWithoutTypes,
77
+ mutationKeyName,
78
+ mutationKey: `[${mutationKey.filter(Boolean).join(', ')}]`,
79
+ };
80
+ });
81
+
82
+ if (mutationConfigs.length > 0) {
83
+ const variableTypes = mutationConfigs
84
+ .filter(config => config.hasVariables)
85
+ .map(config => {
86
+ const headerName = config.route.specificArgs.headers ? `${pascalCase(config.route.routeName.usage)}Headers` : null;
87
+ return `type T${pascalCase(config.functionName)}Variables = { ${config.requestParams}, kyInstance?: KyInstance; options?: ${headerName ? `Omit<Options, 'headers'> & { headers: ${headerName} }` : 'Options'}; };`
88
+ })
89
+ .join('\n');
90
+
91
+ %>
92
+
93
+ <% if (mutationConfigs?.length) { %>
94
+
95
+ import type {
96
+ DefaultError,
97
+ UseMutationOptions,
98
+ useQueryClient,
99
+ } from '@tanstack/react-query';
100
+ import { useMutation } from '@tanstack/react-query';
101
+ import type{ KyInstance, Options } from 'ky';
102
+
103
+ <%~ includeFile('../dto-import.ejs', { ...it }) %>
104
+
105
+ import { <%= moduleName %>Api } from './instance';
106
+
107
+ export const <% ~moduleName.toUpperCase() %>_MUTATION_KEY = {
108
+ <% for (const config of mutationConfigs) { %>
109
+ <% ~config.mutationKeyName %>: <% ~config.mutationKey %>,
110
+ <% } %>
111
+ };
112
+
113
+ const mutations = {
114
+ <% for (const config of mutationConfigs) { %>
115
+ <%= config.functionName %>: () => ({
116
+ mutationFn: <% if (config.hasVariables) { %>(variables: T<%= pascalCase(config.functionName) %>Variables) => {
117
+ const { <%= config.requestParamsWithoutTypes %>, kyInstance, options } = variables;
118
+ return <%= moduleName %>Api.<%= config.functionName %>(<%= config.requestParamsWithoutTypes %>, kyInstance, options);
119
+ }<% } else { %>() => <%= moduleName %>Api.<%= config.functionName %>()<% } %>,
120
+ mutationKey: <% ~moduleName.toUpperCase() %>_MUTATION_KEY.<% ~config.mutationKeyName %>,
121
+ }),
122
+ <% } %>
123
+ };
124
+
125
+ <% for (const mutationConfig of mutationConfigs) {
126
+ const routeDocs = includeFile("../route-docs.ejs", { config, route: mutationConfig.route, utils });
127
+ %>
128
+
129
+ /**
130
+ <%~ routeDocs.lines %>
131
+ */
132
+ export const <%= mutationConfig.hookName %> = (
133
+ options?: Omit<
134
+ UseMutationOptions<<%= mutationConfig.responseDto %>, DefaultError, <%= mutationConfig.hasVariables ? `T${pascalCase(mutationConfig.functionName)}Variables` : 'void' %>>,
135
+ 'mutationFn'|'mutationKey'
136
+ >
137
+ ) => {
138
+ return useMutation({
139
+ ...mutations.<%= mutationConfig.functionName %>(),
140
+ ...options,
141
+ });
142
+ };
143
+
144
+ <% } %>
145
+
146
+ <%~ variableTypes %>
147
+
148
+ <%
149
+ } else { %>
150
+ <% /* mutationConfigs가 비어있을 때는 아무것도 출력되지 않습니다 */ %>
151
+ <% } %>
152
+ <% } %>
@@ -0,0 +1,46 @@
1
+ /**
2
+ * Swagger 문서 가져오기 유틸리티
3
+ * - 원격 Swagger 문서를 HTTP 요청으로 가져옴
4
+ * - HTTP Basic Authentication 지원
5
+ * - JSON 형태의 Swagger 문서 반환
6
+ *
7
+ * @param {string} url - Swagger 문서 URL
8
+ * @param {string} username - HTTP Basic Auth 사용자명 (선택사항)
9
+ * @param {string} password - HTTP Basic Auth 비밀번호 (선택사항)
10
+ * @returns {Promise<Object>} Swagger 문서 JSON 객체
11
+ *
12
+ * @example
13
+ * // 인증 없이 가져오기
14
+ * const swagger = await fetchSwagger('https://api.example.com/swagger.json');
15
+ *
16
+ * // 인증과 함께 가져오기
17
+ * const swagger = await fetchSwagger(
18
+ * 'https://api.example.com/swagger.json',
19
+ * 'admin',
20
+ * 'password'
21
+ * );
22
+ */
23
+ export const fetchSwagger = async (url, username, password) => {
24
+ // HTTP Basic Authentication 헤더 생성
25
+ const credentials = btoa(`${username}:${password}`);
26
+
27
+ try {
28
+ // Swagger 문서 HTTP 요청
29
+ const response = await fetch(url, {
30
+ method: "GET",
31
+ headers: {
32
+ Authorization: `Basic ${credentials}`,
33
+ },
34
+ });
35
+
36
+ // HTTP 응답 상태 확인
37
+ if (!response.ok) {
38
+ console.error("failed with status code", response.status);
39
+ }
40
+
41
+ // JSON 형태로 파싱하여 반환
42
+ return response.json();
43
+ } catch (error) {
44
+ console.error("There was a problem with the fetch operation:", error);
45
+ }
46
+ };
package/utils/file.js ADDED
@@ -0,0 +1,81 @@
1
+ /**
2
+ * 파일 처리 유틸리티 모듈
3
+ * - 파일 및 디렉토리 생성 기능
4
+ * - 생성된 API 파일들을 적절한 위치에 저장
5
+ * - 디렉토리 자동 생성 지원
6
+ */
7
+
8
+ import path from "node:path";
9
+ import fs from "node:fs";
10
+
11
+ /**
12
+ * 지정된 경로에 파일을 생성하는 함수
13
+ * - 필요한 디렉토리를 자동으로 생성
14
+ * - 파일 내용을 UTF-8 인코딩으로 저장
15
+ * - 성공/실패 로그 출력
16
+ *
17
+ * @param {string} filePath - 생성할 파일의 전체 경로
18
+ * @param {string} fileContent - 파일에 저장할 내용
19
+ *
20
+ * @example
21
+ * await writeFileToPath('./src/api/user.ts', 'export class UserApi {}');
22
+ */
23
+ export const writeFileToPath = async (filePath, fileContent) => {
24
+ // 파일 경로에서 디렉토리 경로 추출
25
+ const outputDir = path.dirname(filePath);
26
+
27
+ try {
28
+ // 디렉토리가 존재하지 않으면 재귀적으로 생성
29
+ await fs.promises.mkdir(outputDir, { recursive: true });
30
+
31
+ // 파일 내용을 UTF-8 인코딩으로 저장
32
+ await fs.promises.writeFile(filePath, fileContent);
33
+
34
+ console.log(`✅ Successfully wrote file at ${filePath}`);
35
+ } catch (err) {
36
+ console.error(`☠️ Failed to write file at ${filePath}: ${err}`);
37
+ }
38
+ };
39
+
40
+ /**
41
+ * DTO 파일을 저장하는 함수
42
+ * - 생성된 파일들 중에서 data-contracts 파일을 찾아 저장
43
+ * - DTO 타입 정의들이 포함된 파일
44
+ *
45
+ * @param {string} outputPath - DTO 파일을 저장할 경로
46
+ * @param {Array} generateFiles - 생성된 파일들의 배열
47
+ *
48
+ * @deprecated 현재는 generate-all.js에서 직접 처리하므로 사용되지 않음
49
+ */
50
+ export const saveDto = async (outputPath, generateFiles) => {
51
+ await writeFileToPath(
52
+ outputPath,
53
+ generateFiles.find(({ fileName }) => fileName === "data-contracts")
54
+ .fileContent
55
+ );
56
+ };
57
+
58
+ /**
59
+ * 엔티티별 API 파일들을 저장하는 함수
60
+ * - http-client와 data-contracts를 제외한 모든 파일 저장
61
+ * - 모듈명을 기반으로 경로 동적 생성
62
+ *
63
+ * @param {string} outputPath - 출력 경로 템플릿 ({moduleName} 플레이스홀더 포함)
64
+ * @param {Array} generateFiles - 생성된 파일들의 배열
65
+ *
66
+ * @deprecated 현재는 generate-all.js에서 직접 처리하므로 사용되지 않음
67
+ */
68
+ export const saveEntitiesFile = async (outputPath, generateFiles) => {
69
+ for (const { fileName, fileContent } of generateFiles) {
70
+ // http-client와 data-contracts 파일은 제외
71
+ if (fileName === "http-client" || fileName === "data-contracts") continue;
72
+
73
+ // 파일명을 소문자로 변환하여 모듈명으로 사용
74
+ const moduleName = fileName.toLowerCase();
75
+
76
+ // 출력 경로에서 {moduleName} 플레이스홀더를 실제 모듈명으로 교체
77
+ const output = outputPath.replace("{moduleName}", moduleName);
78
+
79
+ await writeFileToPath(output, fileContent);
80
+ }
81
+ };
@@ -0,0 +1,111 @@
1
+ /**
2
+ * Swagger 스키마 파서 유틸리티
3
+ * - Swagger anyOf 스키마를 TypeScript Union 타입으로 변환
4
+ * - 필수/선택적 필드 처리
5
+ * - null 타입 자동 추가/제거
6
+ */
7
+
8
+ /**
9
+ * 기본 스키마 파서 클래스
10
+ * - swagger-typescript-api의 스키마 파서 기반 클래스
11
+ * - 다양한 스키마 타입 처리를 위한 공통 기능 제공
12
+ */
13
+ class MonoSchemaParser {
14
+ schema;
15
+ typeName;
16
+ schemaPath;
17
+
18
+ schemaParser;
19
+ /** @type {SchemaParserFabric} */
20
+ schemaParserFabric;
21
+ /** @type {TypeNameFormatter} */
22
+ typeNameFormatter;
23
+ /** @type {SchemaComponentsMap} */
24
+ schemaComponentsMap;
25
+ /** @type {SchemaUtils} */
26
+ schemaUtils;
27
+ /** @type {CodeGenConfig} */
28
+ config;
29
+ /** @type {SchemaFormatters} */
30
+ schemaFormatters;
31
+
32
+ /**
33
+ * MonoSchemaParser 생성자
34
+ * @param {Object} schemaParser - 상위 스키마 파서 인스턴스
35
+ * @param {Object} schema - 파싱할 스키마 객체
36
+ * @param {string} typeName - 생성할 타입명
37
+ * @param {Array} schemaPath - 스키마 경로 배열 (기본값: [])
38
+ */
39
+ constructor(schemaParser, schema, typeName, schemaPath = []) {
40
+ this.schemaParser = schemaParser;
41
+ this.schemaParserFabric = schemaParser.schemaParserFabric;
42
+ this.schema = schema;
43
+ this.typeName = typeName;
44
+ this.typeNameFormatter = schemaParser.typeNameFormatter;
45
+ this.schemaPath = schemaPath;
46
+ this.schemaComponentsMap = this.schemaParser.schemaComponentsMap;
47
+ this.schemaUtils = this.schemaParser.schemaUtils;
48
+ this.config = this.schemaParser.config;
49
+ this.schemaFormatters = this.schemaParser.schemaFormatters;
50
+ }
51
+ }
52
+
53
+ /**
54
+ * anyOf 스키마 파서 클래스
55
+ * - Swagger anyOf 스키마를 TypeScript Union 타입 (T1 | T2)으로 변환
56
+ * - 필수/선택적 필드에 따른 null 타입 자동 처리
57
+ * - 중복 타입 제거 및 최적화
58
+ *
59
+ * @example
60
+ * // Swagger anyOf 스키마:
61
+ * {
62
+ * "anyOf": [
63
+ * { "type": "string" },
64
+ * { "type": "number" }
65
+ * ]
66
+ * }
67
+ *
68
+ * // 생성되는 TypeScript 타입:
69
+ * string | number
70
+ */
71
+ class AnyOfSchemaParser extends MonoSchemaParser {
72
+ /**
73
+ * anyOf 스키마를 TypeScript Union 타입으로 파싱
74
+ * @returns {string} TypeScript Union 타입 문자열
75
+ */
76
+ parse() {
77
+ // 제외할 타입들 (any 타입은 항상 제외)
78
+ const ignoreTypes = [this.config.Ts.Keyword.Any];
79
+
80
+ // 필수 필드 여부에 따른 null 타입 처리
81
+ if (this.schema.required === "boolean") {
82
+ // 스키마에 required가 boolean으로 명시된 경우
83
+ !this.schema.required && ignoreTypes.push("null");
84
+ } else {
85
+ // 상위 스키마에서 required 필드 확인
86
+ const isRequired = this.schemaComponentsMap._data
87
+ .find((v) => v.typeName === this.schemaPath.at(0))
88
+ ?.rawTypeData?.required?.includes(this.schemaPath.at(1));
89
+ !isRequired && ignoreTypes.push("null");
90
+ }
91
+
92
+ // anyOf 배열의 각 스키마를 TypeScript 타입으로 변환
93
+ const combined = this.schema.anyOf.map((childSchema) =>
94
+ this.schemaParserFabric.getInlineParseContent(
95
+ this.schemaUtils.makeAddRequiredToChildSchema(this.schema, childSchema),
96
+ this.schemaPath
97
+ )
98
+ );
99
+
100
+ // 제외할 타입들을 필터링하여 최종 타입 배열 생성
101
+ const filtered = this.schemaUtils.filterSchemaContents(
102
+ combined,
103
+ (content) => !ignoreTypes.includes(content)
104
+ );
105
+
106
+ // TypeScript Union 타입 문자열 생성 (예: "string | number")
107
+ return this.config.Ts.UnionType(filtered);
108
+ }
109
+ }
110
+
111
+ export { AnyOfSchemaParser };
@@ -0,0 +1,33 @@
1
+ /**
2
+ * 문자열 처리 유틸리티 모듈
3
+ * - 문자열 케이스 변환 기능
4
+ * - 파일명 생성을 위한 kebab-case 변환
5
+ */
6
+
7
+ /**
8
+ * 문자열을 kebab-case로 변환하는 함수
9
+ * - PascalCase, camelCase, snake_case를 kebab-case로 변환
10
+ * - 공백을 하이픈으로 변환
11
+ * - 모든 문자를 소문자로 변환
12
+ *
13
+ * @param {string} str - 변환할 문자열
14
+ * @returns {string} kebab-case로 변환된 문자열
15
+ *
16
+ * @example
17
+ * toKebabCase('MyApiTitle') // 'my-api-title'
18
+ * toKebabCase('user_management') // 'user-management'
19
+ * toKebabCase('API Documentation') // 'api-documentation'
20
+ */
21
+ export const toKebabCase = (str) => {
22
+ return (
23
+ str
24
+ // PascalCase/camelCase를 kebab-case로 변환 (예: MyApi -> My-Api)
25
+ .replace(/([a-z])([A-Z])/g, "$1-$2")
26
+ // 공백을 하이픈으로 변환
27
+ .replace(/\s+/g, "-")
28
+ // 언더스코어를 하이픈으로 변환
29
+ .replace(/_/g, "-")
30
+ // 모든 문자를 소문자로 변환
31
+ .toLowerCase()
32
+ );
33
+ };
package/utils/url.js ADDED
@@ -0,0 +1,34 @@
1
+ /**
2
+ * URL 처리 유틸리티 모듈
3
+ * - URL 유효성 검증 기능
4
+ * - 로컬 파일과 원격 URL 구분
5
+ */
6
+
7
+ import { fileURLToPath } from "url";
8
+
9
+ const __filename = fileURLToPath(import.meta.url);
10
+
11
+ /**
12
+ * 문자열이 유효한 URL인지 확인하는 함수
13
+ * - HTTP/HTTPS URL 형식 검증
14
+ * - 로컬 파일 경로와 원격 URL 구분에 사용
15
+ *
16
+ * @param {string} string - 검증할 문자열
17
+ * @returns {boolean} 유효한 URL이면 true, 아니면 false
18
+ *
19
+ * @example
20
+ * isUrl('https://api.example.com/swagger.json') // true
21
+ * isUrl('./swagger.yml') // false
22
+ * isUrl('not-a-url') // false
23
+ */
24
+ export const isUrl = (string) => {
25
+ try {
26
+ // URL 생성자를 사용하여 유효성 검증
27
+ // 유효하지 않은 URL이면 예외 발생
28
+ new URL(string);
29
+ return true;
30
+ } catch (err) {
31
+ // URL 생성 실패 시 false 반환
32
+ return false;
33
+ }
34
+ };