@axboot-mcp/mcp-server 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.
- package/CLAUDE.md +119 -0
- package/MCP_TOOL_PLAN.md +710 -0
- package/MCP_USAGE.md +914 -0
- package/README.md +168 -0
- package/REPOSITORY_CONVENTIONS.md +250 -0
- package/SEARCH_PARAMS_MCP_TOOL_COMPLETE_PLAN.md +646 -0
- package/SEARCH_PARAMS_PLAN.md +2570 -0
- package/STORE_PATTERNS.md +1178 -0
- package/debug-dto.js +72 -0
- package/generate-banner-store.js +62 -0
- package/generation-plan.json +2176 -0
- package/generation-results.json +1817 -0
- package/package.json +45 -0
- package/scripts/batch-generate-all.js +159 -0
- package/scripts/batch-generate-mcp.js +329 -0
- package/scripts/batch-generate-stores-v2.js +272 -0
- package/scripts/batch-generate-stores.js +179 -0
- package/scripts/batch-plan.json +3810 -0
- package/scripts/batch-process.py +90 -0
- package/scripts/batch-regenerate.js +356 -0
- package/scripts/direct-generate.js +227 -0
- package/scripts/execute-batches.js +1911 -0
- package/scripts/generate-all-stores.js +144 -0
- package/scripts/generate-stores-mcp.js +161 -0
- package/scripts/generate-stores-v2.js +450 -0
- package/scripts/generate-stores-v3.js +412 -0
- package/scripts/generate-stores-v4.js +521 -0
- package/scripts/generate-stores.js +382 -0
- package/scripts/repos-to-process.json +1899 -0
- package/src/config/nh-layout-patterns.ts +166 -0
- package/src/docs/HOOK_GENERATION_PLAN.md +2226 -0
- package/src/docs/NH_STORE_PATTERNS.md +297 -0
- package/src/docs/README.md +216 -0
- package/src/docs/index.ts +28 -0
- package/src/docs/loader.ts +568 -0
- package/src/docs/patterns.json +419 -0
- package/src/docs/practical-examples.md +732 -0
- package/src/docs/quick-start.md +257 -0
- package/src/docs/requirements-analysis-guide.md +364 -0
- package/src/docs/rules.json +321 -0
- package/src/docs/store-pattern-analysis.md +664 -0
- package/src/docs/store-patterns-rules.md +1168 -0
- package/src/docs/store-patterns-usage-guide.md +1835 -0
- package/src/docs/troubleshooting.md +544 -0
- package/src/docs/type-selection-guide.md +572 -0
- package/src/docs//354/202/254/354/232/251/353/262/225/AntD-/354/273/264/355/217/254/353/204/214/355/212/270-/354/202/254/354/232/251/353/262/225.md +1515 -0
- package/src/docs//354/202/254/354/232/251/353/262/225/DataGrid-/354/202/254/354/232/251/353/262/225.md +866 -0
- package/src/docs//354/202/254/354/232/251/353/262/225/FormItem-/354/202/254/354/232/251/353/262/225.md +903 -0
- package/src/docs//354/202/254/354/232/251/353/262/225/FormModal-/354/202/254/354/232/251/353/262/225.md +1155 -0
- package/src/docs//354/202/254/354/232/251/353/262/225/MCP-/353/260/224/354/235/264/353/270/214/354/275/224/353/224/251-/352/260/200/354/235/264/353/223/234.md +1133 -0
- package/src/docs//354/202/254/354/232/251/353/262/225/MSW-Mock-/353/215/260/354/235/264/355/204/260-/354/202/254/354/232/251/353/262/225.md +579 -0
- package/src/docs//354/202/254/354/232/251/353/262/225/Search-/354/273/264/355/217/254/353/204/214/355/212/270-/354/202/254/354/232/251/353/262/225.md +738 -0
- package/src/docs//354/202/254/354/232/251/353/262/225/Store-/355/214/250/355/204/264-/354/202/254/354/232/251/353/262/225.md +1135 -0
- package/src/docs//354/202/254/354/232/251/353/262/225//355/231/224/353/251/264/352/265/254/354/204/261-/355/203/200/354/236/205/353/263/204-/352/260/234/353/260/234/354/210/234/354/204/234.md +1805 -0
- package/src/docs//354/202/254/354/232/251/353/262/225//355/231/224/353/251/264/355/203/200/354/236/205/353/263/204-/352/260/234/353/260/234-/355/224/204/353/241/254/355/224/204/355/212/270-/352/260/200/354/235/264/353/223/234.md +946 -0
- package/src/docs//354/202/254/354/232/251/353/262/225//355/231/225/354/236/245/355/231/224/353/251/264/355/203/200/354/236/205/353/263/204-/354/203/201/354/204/270-/355/224/204/353/241/254/355/224/204/355/212/270/352/260/200/354/235/264/353/223/234.md +2422 -0
- package/src/features/store-features.ts +232 -0
- package/src/handlers/analyze-requirements.ts +403 -0
- package/src/handlers/analyze.ts +1373 -0
- package/src/handlers/generate-from-requirements.ts +250 -0
- package/src/handlers/generate-hook.ts +950 -0
- package/src/handlers/generate-interactive.ts +840 -0
- package/src/handlers/generate-listdatagrid.ts +521 -0
- package/src/handlers/generate-multi-stores.ts +577 -0
- package/src/handlers/generate-requirements-from-layout.ts +160 -0
- package/src/handlers/generate-search-params.ts +717 -0
- package/src/handlers/generate.ts +911 -0
- package/src/handlers/list-templates.ts +104 -0
- package/src/handlers/scan-metadata.ts +485 -0
- package/src/handlers/suggest-layout.ts +326 -0
- package/src/index.ts +959 -0
- package/src/prompts/search-params.md +793 -0
- package/src/templates/index.ts +107 -0
- package/src/templates/unified.ts +462 -0
- package/store-generation-error-patterns.md +225 -0
- package/test/useAgentStore.ts +136 -0
- package/test-server.js +78 -0
- package/tsconfig.json +20 -0
|
@@ -0,0 +1,521 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* StoreNew.ts 파일 생성 스크립트 v4 (타입 오류 수정)
|
|
3
|
+
* 리포지토리 파일을 분석하여 StoreNew.ts 파일 생성
|
|
4
|
+
*/
|
|
5
|
+
|
|
6
|
+
const fs = require('fs');
|
|
7
|
+
const path = require('path');
|
|
8
|
+
|
|
9
|
+
// 경로 설정
|
|
10
|
+
const REPO_DIR = '/Users/kyle/Desktop/nh-fe-bo/src/services/@interface/repository';
|
|
11
|
+
const STORE_DIR = '/Users/kyle/Desktop/nh-fe-bo/src/pages/resources/NH';
|
|
12
|
+
|
|
13
|
+
// 기본 템플릿 (Type 6: Simple List)
|
|
14
|
+
const SIMPLE_LIST_TEMPLATE = `import { AXDGDataItem, AXDGPage, AXDGSortParam } from "@axboot/datagrid";
|
|
15
|
+
import { pageStoreActions } from "@core/stores/pageStoreActions";
|
|
16
|
+
import { StoreActions } from "@core/stores/types";
|
|
17
|
+
import { PageStoreActions } from "@core/stores/types";
|
|
18
|
+
import { deleteEmptyValue } from "@core/utils/object";
|
|
19
|
+
import { ProgramFn } from "@types";
|
|
20
|
+
{{IMPORTS}}
|
|
21
|
+
import { getTabStoreListener } from "stores";
|
|
22
|
+
import { create } from "zustand";
|
|
23
|
+
import { subscribeWithSelector } from "zustand/middleware";
|
|
24
|
+
import { shallow } from "zustand/shallow";
|
|
25
|
+
|
|
26
|
+
interface ListRequest extends {{LIST_REQUEST}} {}
|
|
27
|
+
|
|
28
|
+
interface DtoItem extends {{DTO_ITEM}} {}
|
|
29
|
+
|
|
30
|
+
// ========== interface MetaData ==========
|
|
31
|
+
interface MetaData {
|
|
32
|
+
programFn?: ProgramFn;
|
|
33
|
+
listRequestValue: ListRequest;
|
|
34
|
+
listColWidths: number[];
|
|
35
|
+
listSortParams: AXDGSortParam[];
|
|
36
|
+
listSelectedRowKey?: React.Key;
|
|
37
|
+
}
|
|
38
|
+
|
|
39
|
+
// ========== interface States ==========
|
|
40
|
+
interface States extends MetaData {
|
|
41
|
+
routePath?: string;
|
|
42
|
+
listSpinning: boolean;
|
|
43
|
+
listData: AXDGDataItem<DtoItem>[];
|
|
44
|
+
listPage: AXDGPage;
|
|
45
|
+
selectedItem?: DtoItem;
|
|
46
|
+
}
|
|
47
|
+
|
|
48
|
+
// ========== const createState ==========
|
|
49
|
+
const createState: States = {
|
|
50
|
+
listRequestValue: {{DEFAULT_REQUEST_VALUES}},
|
|
51
|
+
listColWidths: [],
|
|
52
|
+
listSpinning: false,
|
|
53
|
+
listData: [],
|
|
54
|
+
listPage: {
|
|
55
|
+
currentPage: 0,
|
|
56
|
+
totalPages: 0,
|
|
57
|
+
},
|
|
58
|
+
listSortParams: [],
|
|
59
|
+
selectedItem: undefined,
|
|
60
|
+
};
|
|
61
|
+
|
|
62
|
+
// ========== interface Actions ==========
|
|
63
|
+
interface Actions extends PageStoreActions<States> {
|
|
64
|
+
setListRequestValue: (requestValue: ListRequest) => void;
|
|
65
|
+
setListColWidths: (colWidths: number[]) => void;
|
|
66
|
+
setListSortParams: (sortParams: AXDGSortParam[]) => void;
|
|
67
|
+
setListSpinning: (spinning: boolean) => void;
|
|
68
|
+
callListApi: (request?: ListRequest) => Promise<void>;
|
|
69
|
+
changeListPage: (currentPage: number, pageSize?: number) => Promise<void>;
|
|
70
|
+
setListSelectedRowKey: (key?: React.Key) => void;
|
|
71
|
+
setSelectedItem: (selectedItem?: DtoItem) => void;
|
|
72
|
+
}
|
|
73
|
+
|
|
74
|
+
// ========== const createActions ==========
|
|
75
|
+
const createActions: StoreActions<States & Actions, Actions> = (set, get) => ({
|
|
76
|
+
...pageStoreActions(set, get, { createState }),
|
|
77
|
+
syncMetadata: (s = createState) => set(s),
|
|
78
|
+
onMountApp: async () => {},
|
|
79
|
+
|
|
80
|
+
setListRequestValue: (requestValues) => set({ listRequestValue: requestValues }),
|
|
81
|
+
setListColWidths: (colWidths) => set({ listColWidths: colWidths }),
|
|
82
|
+
setListSortParams: (sortParams) => set({ listSortParams: sortParams }),
|
|
83
|
+
|
|
84
|
+
setListSpinning: (spinning) => set({ listSpinning: spinning }),
|
|
85
|
+
|
|
86
|
+
{{LIST_METHOD_COMMENT}}
|
|
87
|
+
callListApi: async (request) => {
|
|
88
|
+
if (get().listSpinning) return;
|
|
89
|
+
set({ listSpinning: true });
|
|
90
|
+
|
|
91
|
+
try {
|
|
92
|
+
const apiParam: ListRequest = { ...get().listRequestValue, ...request };
|
|
93
|
+
const response = await {{SERVICE_NAME}}.{{LIST_METHOD_NAME}}(deleteEmptyValue(apiParam));
|
|
94
|
+
|
|
95
|
+
set({
|
|
96
|
+
listRequestValue: apiParam,
|
|
97
|
+
listData: response.ds.map((values) => ({ values })),
|
|
98
|
+
listPage: {
|
|
99
|
+
currentPage: response.page.pageNumber ?? 1,
|
|
100
|
+
pageSize: response.page.pageSize ?? 0,
|
|
101
|
+
totalPages: response.page.pageCount ?? 0,
|
|
102
|
+
totalElements: response.page?.totalCount,
|
|
103
|
+
},
|
|
104
|
+
});
|
|
105
|
+
} finally {
|
|
106
|
+
set({ listSpinning: false });
|
|
107
|
+
}
|
|
108
|
+
},
|
|
109
|
+
|
|
110
|
+
changeListPage: async (pageNumber, pageSize) => {
|
|
111
|
+
await get().callListApi({ pageNumber, pageSize });
|
|
112
|
+
},
|
|
113
|
+
|
|
114
|
+
setListSelectedRowKey: (key) => set({ listSelectedRowKey: key }),
|
|
115
|
+
|
|
116
|
+
setSelectedItem: (selectedItem) => set({ selectedItem }),
|
|
117
|
+
});
|
|
118
|
+
|
|
119
|
+
// ========== export ==========
|
|
120
|
+
export interface {{STORE_INTERFACE}} extends States, Actions, PageStoreActions<States> {}
|
|
121
|
+
|
|
122
|
+
export const {{STORE_NAME}} = create(
|
|
123
|
+
subscribeWithSelector<{{STORE_INTERFACE}}>((set, get) => ({
|
|
124
|
+
...createState,
|
|
125
|
+
...createActions(set, get),
|
|
126
|
+
})),
|
|
127
|
+
);
|
|
128
|
+
|
|
129
|
+
// ========== Store.subscribe ==========
|
|
130
|
+
{{STORE_NAME}}.subscribe(
|
|
131
|
+
(s): Record<keyof MetaData, any> => ({
|
|
132
|
+
programFn: s.programFn,
|
|
133
|
+
listRequestValue: s.listRequestValue,
|
|
134
|
+
listColWidths: s.listColWidths,
|
|
135
|
+
listSortParams: s.listSortParams,
|
|
136
|
+
listSelectedRowKey: s.listSelectedRowKey
|
|
137
|
+
}),
|
|
138
|
+
getTabStoreListener<MetaData>(createState.routePath),
|
|
139
|
+
{ equalityFn: shallow },
|
|
140
|
+
);
|
|
141
|
+
`;
|
|
142
|
+
|
|
143
|
+
/**
|
|
144
|
+
* 리포지토리 파일에서 정보 추출
|
|
145
|
+
*/
|
|
146
|
+
function analyzeRepository(filePath) {
|
|
147
|
+
const content = fs.readFileSync(filePath, 'utf-8');
|
|
148
|
+
|
|
149
|
+
// 클래스 이름 추출
|
|
150
|
+
const classNameMatch = content.match(/export class (\w+)Repository/);
|
|
151
|
+
const className = classNameMatch ? classNameMatch[1] : '';
|
|
152
|
+
|
|
153
|
+
// Service 이름 추출 - Repository → Service (같은 패턴 유지)
|
|
154
|
+
const serviceName = className.replace('Repository', '') + 'Service';
|
|
155
|
+
|
|
156
|
+
// 인터페이스 추출
|
|
157
|
+
const interfaceMatch = content.match(/extends (\w+)Interface/);
|
|
158
|
+
const interfaceName = interfaceMatch ? interfaceMatch[1] : className;
|
|
159
|
+
|
|
160
|
+
// 메서드 추출
|
|
161
|
+
// imports 추출 및 메서드 분석 (중복 제거)
|
|
162
|
+
const importsSet = new Set();
|
|
163
|
+
const methods = [];
|
|
164
|
+
const methodBlocks = content.split(/async\s+/).slice(1);
|
|
165
|
+
|
|
166
|
+
// 기본 imports 추가 (배열 타입 제외, Interface 타입 제외)
|
|
167
|
+
const importMatch = content.match(/import\s*\{([^}]+)\}\s*from\s*"[\.\.\/]+interface"/);
|
|
168
|
+
if (importMatch) {
|
|
169
|
+
importMatch[1].split(',').forEach(item => {
|
|
170
|
+
const trimmed = item.trim();
|
|
171
|
+
// 배열 타입 제외 ([]로 끝나는 타입)
|
|
172
|
+
// Interface 타입 제외 (Interface로 끝나는 타입은 Store에서 불필요)
|
|
173
|
+
if (trimmed && !trimmed.endsWith('[]') && !trimmed.endsWith('Interface')) {
|
|
174
|
+
importsSet.add(trimmed);
|
|
175
|
+
}
|
|
176
|
+
});
|
|
177
|
+
}
|
|
178
|
+
|
|
179
|
+
// 메서드에서 사용되는 비배열 Response 타입도 추가
|
|
180
|
+
// (원본 import에 없는 타입만 추가)
|
|
181
|
+
methodBlocks.forEach(block => {
|
|
182
|
+
const apiWrapperMatch = block.match(/this\._apiWrapper<([^>]+)>/);
|
|
183
|
+
if (!apiWrapperMatch) return;
|
|
184
|
+
|
|
185
|
+
const returnType = apiWrapperMatch[1];
|
|
186
|
+
|
|
187
|
+
// Response 타입 추가 (배열 제외)
|
|
188
|
+
if (returnType && returnType !== 'void') {
|
|
189
|
+
// 배열 타입인 경우 비배열 버전 추가
|
|
190
|
+
if (returnType.endsWith('[]')) {
|
|
191
|
+
const nonArrayType = returnType.slice(0, -2);
|
|
192
|
+
if (!nonArrayType.endsWith('Interface')) {
|
|
193
|
+
importsSet.add(nonArrayType);
|
|
194
|
+
}
|
|
195
|
+
} else if (!returnType.endsWith('Interface')) {
|
|
196
|
+
importsSet.add(returnType);
|
|
197
|
+
}
|
|
198
|
+
}
|
|
199
|
+
});
|
|
200
|
+
|
|
201
|
+
// 메서드 추출 (주석 포함)
|
|
202
|
+
methodBlocks.forEach((block, index) => {
|
|
203
|
+
const methodNameMatch = block.match(/(\w+)\s*\(([^)]*)\)/);
|
|
204
|
+
if (!methodNameMatch) return;
|
|
205
|
+
|
|
206
|
+
const methodName = methodNameMatch[1];
|
|
207
|
+
const paramsStr = methodNameMatch[2];
|
|
208
|
+
|
|
209
|
+
// 파라미터 타입 추출 (배열 타입 제거)
|
|
210
|
+
let requestType = null;
|
|
211
|
+
const params = paramsStr.split(',').map(p => p.trim());
|
|
212
|
+
for (const p of params) {
|
|
213
|
+
if (p.includes(':') && p.includes('Request') && !p.includes('[]')) {
|
|
214
|
+
const type = p.split(':')[1]?.trim();
|
|
215
|
+
// 일반 타입 제외 (ApiRequestConfig 등)
|
|
216
|
+
if (type !== 'ApiRequestConfig' && type !== 'AxiosResponse') {
|
|
217
|
+
requestType = type;
|
|
218
|
+
break;
|
|
219
|
+
}
|
|
220
|
+
}
|
|
221
|
+
}
|
|
222
|
+
|
|
223
|
+
// _apiWrapper 호출에서 타입 추출
|
|
224
|
+
const apiWrapperMatch = block.match(/this\._apiWrapper<([^>]+)>/);
|
|
225
|
+
if (!apiWrapperMatch) return;
|
|
226
|
+
|
|
227
|
+
const returnType = apiWrapperMatch[1];
|
|
228
|
+
|
|
229
|
+
// Response 타입 추가 (배열 타입 제외)
|
|
230
|
+
if (returnType && returnType !== 'void' && !returnType.includes('[]')) {
|
|
231
|
+
importsSet.add(returnType);
|
|
232
|
+
}
|
|
233
|
+
|
|
234
|
+
// 엔드포인트 추출
|
|
235
|
+
const endpointMatch = block.match(/`([^`]+)`/);
|
|
236
|
+
const endpoint = endpointMatch ? endpointMatch[1] : '';
|
|
237
|
+
|
|
238
|
+
// 메서드 주석 추출 (원본 파일에서 /* 주석 */ 형태)
|
|
239
|
+
let comment = null;
|
|
240
|
+
// 정확한 메서드 이름 매칭을 위한 regex (단어 경계 사용)
|
|
241
|
+
const methodRegex = new RegExp(`async\\s+(${methodName.replace(/[.*+?^${}()|[\]\\]/g, '\\$&')})\\s*\\(`);
|
|
242
|
+
const methodMatch = content.match(methodRegex);
|
|
243
|
+
if (methodMatch) {
|
|
244
|
+
const methodIndex = methodMatch.index;
|
|
245
|
+
// 메서드 바로 앞의 내용 추출
|
|
246
|
+
const contentBeforeMethod = content.substring(0, methodIndex).trim();
|
|
247
|
+
// 마지막 주석 찾기
|
|
248
|
+
const lastCommentStart = contentBeforeMethod.lastIndexOf('/*');
|
|
249
|
+
if (lastCommentStart !== -1) {
|
|
250
|
+
const lastCommentEnd = contentBeforeMethod.indexOf('*/', lastCommentStart);
|
|
251
|
+
if (lastCommentEnd !== -1) {
|
|
252
|
+
const commentCandidate = contentBeforeMethod.substring(lastCommentStart, lastCommentEnd + 2).trim();
|
|
253
|
+
// 주석과 메서드 사이의 내용 확인 (공백/줄바꿈만 허용)
|
|
254
|
+
const betweenCommentAndMethod = contentBeforeMethod.substring(lastCommentEnd + 2).trim();
|
|
255
|
+
// 주석 바로 다음에 이 메서드가 오는지 확인 (다른 async 키워드가 있으면 안 됨)
|
|
256
|
+
const nextAsyncIndex = betweenCommentAndMethod.lastIndexOf('async ');
|
|
257
|
+
if (nextAsyncIndex === -1 || betweenCommentAndMethod.substring(nextAsyncIndex + 6).trim().startsWith(methodName)) {
|
|
258
|
+
comment = commentCandidate;
|
|
259
|
+
}
|
|
260
|
+
}
|
|
261
|
+
}
|
|
262
|
+
}
|
|
263
|
+
|
|
264
|
+
methods.push({
|
|
265
|
+
name: methodName,
|
|
266
|
+
requestType,
|
|
267
|
+
returnType,
|
|
268
|
+
endpoint,
|
|
269
|
+
comment,
|
|
270
|
+
});
|
|
271
|
+
});
|
|
272
|
+
|
|
273
|
+
const imports = Array.from(importsSet);
|
|
274
|
+
|
|
275
|
+
return {
|
|
276
|
+
className,
|
|
277
|
+
serviceName,
|
|
278
|
+
interfaceName,
|
|
279
|
+
methods,
|
|
280
|
+
imports,
|
|
281
|
+
};
|
|
282
|
+
}
|
|
283
|
+
|
|
284
|
+
/**
|
|
285
|
+
* Service 이름 확인 (실제 프로젝트 기반)
|
|
286
|
+
*/
|
|
287
|
+
function getServiceName(repoClassName) {
|
|
288
|
+
// Repository → Service 변환 (같은 패턴)
|
|
289
|
+
return repoClassName.replace('Repository', '') + 'Service';
|
|
290
|
+
}
|
|
291
|
+
|
|
292
|
+
/**
|
|
293
|
+
* DTO 타입 이름 추정 - Repository 정보만 사용
|
|
294
|
+
*/
|
|
295
|
+
function guessDtoType(returnType, repoClassName, repoInfo) {
|
|
296
|
+
// List Response 타입을 그대로 사용 (가장 안전)
|
|
297
|
+
if (returnType && returnType !== 'void' && !returnType.includes('[]')) {
|
|
298
|
+
return returnType;
|
|
299
|
+
}
|
|
300
|
+
|
|
301
|
+
// fallback
|
|
302
|
+
return 'Record<string, any>';
|
|
303
|
+
}
|
|
304
|
+
|
|
305
|
+
/**
|
|
306
|
+
* 기존 Store 파일에서 Request 초기값 추출 (MCP 모드에서는 사용 안 함)
|
|
307
|
+
*/
|
|
308
|
+
function extractDefaultRequestValues(baseName) {
|
|
309
|
+
return '{\n pageNumber: 1,\n pageSize: 100,\n }';
|
|
310
|
+
}
|
|
311
|
+
|
|
312
|
+
/**
|
|
313
|
+
* 템플릿 변수 생성
|
|
314
|
+
*/
|
|
315
|
+
function createTemplateVariables(repoInfo) {
|
|
316
|
+
const className = repoInfo.className;
|
|
317
|
+
const baseName = className.replace('Repository', '');
|
|
318
|
+
|
|
319
|
+
// LIST 메서드 찾기
|
|
320
|
+
const listMethod = repoInfo.methods.find(m =>
|
|
321
|
+
m.name.toLowerCase().includes('list') &&
|
|
322
|
+
!m.name.toLowerCase().includes('excel')
|
|
323
|
+
);
|
|
324
|
+
|
|
325
|
+
// LIST 메서드 주석 추출
|
|
326
|
+
const listMethodComment = listMethod?.comment || null;
|
|
327
|
+
|
|
328
|
+
// Request 타입 결정
|
|
329
|
+
let listRequestType = 'Partial<Record<string, any>>';
|
|
330
|
+
let defaultRequestValues = '{\n pageNumber: 1,\n pageSize: 100,\n }';
|
|
331
|
+
let listResponseType = 'any';
|
|
332
|
+
let dtoItem = 'Record<string, any>';
|
|
333
|
+
|
|
334
|
+
if (listMethod) {
|
|
335
|
+
listResponseType = listMethod.returnType;
|
|
336
|
+
|
|
337
|
+
if (listMethod.requestType) {
|
|
338
|
+
listRequestType = listMethod.requestType;
|
|
339
|
+
// 기존 Store에서 초기값 추출
|
|
340
|
+
defaultRequestValues = extractDefaultRequestValues(baseName);
|
|
341
|
+
} else if (listResponseType.endsWith('Response')) {
|
|
342
|
+
listRequestType = listResponseType.replace('Response', 'Request');
|
|
343
|
+
}
|
|
344
|
+
|
|
345
|
+
// DTO 타입 추정
|
|
346
|
+
dtoItem = guessDtoType(listResponseType, className, repoInfo);
|
|
347
|
+
}
|
|
348
|
+
|
|
349
|
+
// imports 생성 - Repository의 import만 사용
|
|
350
|
+
const allImports = new Set(repoInfo.imports);
|
|
351
|
+
|
|
352
|
+
// 일반 타입 제거 (ApiRequestConfig 등)
|
|
353
|
+
const genericTypes = new Set(['ApiRequestConfig', 'AxiosResponse', 'Promise']);
|
|
354
|
+
genericTypes.forEach(type => allImports.delete(type));
|
|
355
|
+
|
|
356
|
+
// imports 정렬
|
|
357
|
+
const sortedImports = Array.from(allImports).sort();
|
|
358
|
+
|
|
359
|
+
// Service import 생성
|
|
360
|
+
const serviceName = getServiceName(className);
|
|
361
|
+
const importsLine = sortedImports.length > 0
|
|
362
|
+
? `import { ${[...sortedImports, serviceName].join(', ')} } from "services";`
|
|
363
|
+
: `import { ${serviceName} } from "services";`;
|
|
364
|
+
|
|
365
|
+
// Store 이름 생성
|
|
366
|
+
const storeName = `use${baseName}Store`;
|
|
367
|
+
const storeInterface = `${baseName}Store`;
|
|
368
|
+
|
|
369
|
+
return {
|
|
370
|
+
STORE_NAME: storeName,
|
|
371
|
+
STORE_NAME_PASCAL: baseName,
|
|
372
|
+
STORE_INTERFACE: storeInterface,
|
|
373
|
+
SERVICE_NAME: getServiceName(className),
|
|
374
|
+
LIST_METHOD_NAME: listMethod?.name || 'getList',
|
|
375
|
+
LIST_REQUEST: listRequestType,
|
|
376
|
+
LIST_RESPONSE: listResponseType,
|
|
377
|
+
DTO_ITEM: dtoItem,
|
|
378
|
+
IMPORTS: importsLine,
|
|
379
|
+
DEFAULT_REQUEST_VALUES: defaultRequestValues,
|
|
380
|
+
LIST_METHOD_COMMENT: listMethodComment || '',
|
|
381
|
+
};
|
|
382
|
+
}
|
|
383
|
+
|
|
384
|
+
/**
|
|
385
|
+
* Store 파일 경로 결정 (Repository 이름으로만 결정 - 개선된 매칭)
|
|
386
|
+
*/
|
|
387
|
+
function determineStorePath(baseName) {
|
|
388
|
+
// 여러 단어로 구성된 이름을 dot-case로 변환 (폴더명은 dot 사용)
|
|
389
|
+
// 예: B2bEstimateConsulting → b2b.estimate.consulting
|
|
390
|
+
const dotName = baseName
|
|
391
|
+
.replace(/([a-z])([A-Z])/g, '$1.$2')
|
|
392
|
+
.replace(/^b2b\./i, 'b2b.')
|
|
393
|
+
.toLowerCase();
|
|
394
|
+
|
|
395
|
+
// NH 하위의 모든 디렉토리 검색
|
|
396
|
+
const dirs = fs.readdirSync(STORE_DIR, { withFileTypes: true })
|
|
397
|
+
.filter(d => d.isDirectory())
|
|
398
|
+
.map(d => d.name);
|
|
399
|
+
|
|
400
|
+
// 각 폴더별 매칭 점수 계산
|
|
401
|
+
const scoredDirs = dirs.map(dir => {
|
|
402
|
+
const dirLower = dir.toLowerCase();
|
|
403
|
+
let score = 0;
|
|
404
|
+
|
|
405
|
+
// 정확히 일치하면 최고 점수
|
|
406
|
+
if (dirLower === dotName) {
|
|
407
|
+
return { dir, score: 1000 };
|
|
408
|
+
}
|
|
409
|
+
|
|
410
|
+
// 단어별 정확한 일치 (dot과 hyphen 모두 지원)
|
|
411
|
+
const dirWords = dirLower.split(/[\.-]/);
|
|
412
|
+
const repoWords = dotName.split('.');
|
|
413
|
+
repoWords.forEach(repoWord => {
|
|
414
|
+
if (dirWords.includes(repoWord)) {
|
|
415
|
+
score += 10; // 단어 정확히 일치
|
|
416
|
+
}
|
|
417
|
+
});
|
|
418
|
+
|
|
419
|
+
// 부분 문자열 일치 (더 긴 쪽에 높은 점수)
|
|
420
|
+
if (dirLower.includes(dotName.replace(/\./g, '.'))) {
|
|
421
|
+
score += 5;
|
|
422
|
+
} else if (dotName.replace(/\./g, '').includes(dirLower.replace(/[\.-]/g, ''))) {
|
|
423
|
+
score += 3;
|
|
424
|
+
}
|
|
425
|
+
|
|
426
|
+
return { dir, score };
|
|
427
|
+
});
|
|
428
|
+
|
|
429
|
+
// 점수가 가장 높은 폴더 선택 (최소 10점 이상)
|
|
430
|
+
const bestMatch = scoredDirs.filter(d => d.score >= 10)
|
|
431
|
+
.sort((a, b) => b.score - a.score)[0];
|
|
432
|
+
|
|
433
|
+
if (bestMatch) {
|
|
434
|
+
return path.join(STORE_DIR, bestMatch.dir, `use${baseName}StoreNew.ts`);
|
|
435
|
+
}
|
|
436
|
+
|
|
437
|
+
// 매칭되는 폴더가 없으면 루트에 생성
|
|
438
|
+
return path.join(STORE_DIR, `use${baseName}StoreNew.ts`);
|
|
439
|
+
}
|
|
440
|
+
|
|
441
|
+
/**
|
|
442
|
+
* 템플릿 렌더링
|
|
443
|
+
*/
|
|
444
|
+
function renderTemplate(template, variables) {
|
|
445
|
+
let result = template;
|
|
446
|
+
|
|
447
|
+
Object.keys(variables).forEach(key => {
|
|
448
|
+
const regex = new RegExp(`\\{\\{${key}\\}\\}`, 'g');
|
|
449
|
+
result = result.replace(regex, variables[key]);
|
|
450
|
+
});
|
|
451
|
+
|
|
452
|
+
return result;
|
|
453
|
+
}
|
|
454
|
+
|
|
455
|
+
/**
|
|
456
|
+
* 메인 실행 함수
|
|
457
|
+
*/
|
|
458
|
+
function main() {
|
|
459
|
+
console.log('=== StoreNew.ts 파일 생성 시작 v4 (타입 오류 수정) ===\n');
|
|
460
|
+
|
|
461
|
+
const repoFiles = fs.readdirSync(REPO_DIR)
|
|
462
|
+
.filter(file => file.endsWith('Repository.ts') && file !== 'APIRepository.ts');
|
|
463
|
+
|
|
464
|
+
console.log(`리포지토리 파일 ${repoFiles.length}개 발견\n`);
|
|
465
|
+
|
|
466
|
+
let successCount = 0;
|
|
467
|
+
let skipCount = 0;
|
|
468
|
+
let errorCount = 0;
|
|
469
|
+
|
|
470
|
+
repoFiles.forEach((file, index) => {
|
|
471
|
+
try {
|
|
472
|
+
const repoPath = path.join(REPO_DIR, file);
|
|
473
|
+
const repoInfo = analyzeRepository(repoPath);
|
|
474
|
+
|
|
475
|
+
if (!repoInfo.className || repoInfo.methods.length === 0) {
|
|
476
|
+
console.log(`[${index + 1}/${repoFiles.length}] SKIP: ${file} - 메서드를 찾을 수 없음`);
|
|
477
|
+
skipCount++;
|
|
478
|
+
return;
|
|
479
|
+
}
|
|
480
|
+
|
|
481
|
+
const baseName = repoInfo.className.replace('Repository', '');
|
|
482
|
+
|
|
483
|
+
// 템플릿 변수 생성
|
|
484
|
+
const variables = createTemplateVariables(repoInfo);
|
|
485
|
+
|
|
486
|
+
// 템플릿 렌더링
|
|
487
|
+
const rendered = renderTemplate(SIMPLE_LIST_TEMPLATE, variables);
|
|
488
|
+
|
|
489
|
+
// 출력 경로 결정
|
|
490
|
+
const outputPath = determineStorePath(baseName);
|
|
491
|
+
|
|
492
|
+
// 디렉토리 생성
|
|
493
|
+
const outputDir = path.dirname(outputPath);
|
|
494
|
+
if (!fs.existsSync(outputDir)) {
|
|
495
|
+
fs.mkdirSync(outputDir, { recursive: true });
|
|
496
|
+
}
|
|
497
|
+
|
|
498
|
+
// 파일 쓰기
|
|
499
|
+
fs.writeFileSync(outputPath, rendered);
|
|
500
|
+
|
|
501
|
+
console.log(`[${index + 1}/${repoFiles.length}] SUCCESS: ${baseName}StoreNew.ts 생성 (${variables.LIST_METHOD_NAME})`);
|
|
502
|
+
successCount++;
|
|
503
|
+
|
|
504
|
+
} catch (error) {
|
|
505
|
+
console.error(`[${index + 1}/${repoFiles.length}] ERROR: ${file} - ${error.message}`);
|
|
506
|
+
errorCount++;
|
|
507
|
+
}
|
|
508
|
+
});
|
|
509
|
+
|
|
510
|
+
console.log(`\n=== 생성 완료 ===`);
|
|
511
|
+
console.log(`성공: ${successCount}`);
|
|
512
|
+
console.log(`건너뜀: ${skipCount}`);
|
|
513
|
+
console.log(`오류: ${errorCount}`);
|
|
514
|
+
}
|
|
515
|
+
|
|
516
|
+
// 실행
|
|
517
|
+
if (require.main === module) {
|
|
518
|
+
main();
|
|
519
|
+
}
|
|
520
|
+
|
|
521
|
+
module.exports = { analyzeRepository, createTemplateVariables, renderTemplate };
|