@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,90 @@
|
|
|
1
|
+
#!/usr/bin/env python3
|
|
2
|
+
"""
|
|
3
|
+
Batch Store Generator using MCP-AXBOOT
|
|
4
|
+
Processes all repositories and generates Zustand stores
|
|
5
|
+
"""
|
|
6
|
+
|
|
7
|
+
import json
|
|
8
|
+
import sys
|
|
9
|
+
import subprocess
|
|
10
|
+
import os
|
|
11
|
+
from pathlib import Path
|
|
12
|
+
|
|
13
|
+
# Load the repository list
|
|
14
|
+
plan_file = Path(__file__).parent / 'repos-to-process.json'
|
|
15
|
+
with open(plan_file) as f:
|
|
16
|
+
repositories = json.load(f)
|
|
17
|
+
|
|
18
|
+
print(f"Total repositories to process: {len(repositories)}")
|
|
19
|
+
print("=" * 80)
|
|
20
|
+
|
|
21
|
+
# Track results
|
|
22
|
+
results = {
|
|
23
|
+
"success": [],
|
|
24
|
+
"failed": [],
|
|
25
|
+
"skipped": []
|
|
26
|
+
}
|
|
27
|
+
|
|
28
|
+
# Process each repository
|
|
29
|
+
for i, repo in enumerate(repositories, 1):
|
|
30
|
+
repo_name = repo['repositoryName']
|
|
31
|
+
store_name = repo['storeName']
|
|
32
|
+
input_path = repo['inputPath']
|
|
33
|
+
output_path = repo['outputPath']
|
|
34
|
+
store_type = repo['storeType']
|
|
35
|
+
|
|
36
|
+
print(f"\n[{i}/{len(repositories)}] Processing: {repo_name}")
|
|
37
|
+
print(f" Store: {store_name}")
|
|
38
|
+
print(f" Type: {store_type}")
|
|
39
|
+
print(f" Input: {input_path}")
|
|
40
|
+
print(f" Output: {output_path}")
|
|
41
|
+
|
|
42
|
+
# Check if input file exists
|
|
43
|
+
if not os.path.exists(input_path):
|
|
44
|
+
print(f" ⚠️ SKIP: Input file not found")
|
|
45
|
+
results["skipped"].append({
|
|
46
|
+
"repository": repo_name,
|
|
47
|
+
"reason": "Input file not found"
|
|
48
|
+
})
|
|
49
|
+
continue
|
|
50
|
+
|
|
51
|
+
# Check if file is empty
|
|
52
|
+
if os.path.getsize(input_path) == 0:
|
|
53
|
+
print(f" ⚠️ SKIP: Empty file")
|
|
54
|
+
results["skipped"].append({
|
|
55
|
+
"repository": repo_name,
|
|
56
|
+
"reason": "Empty file"
|
|
57
|
+
})
|
|
58
|
+
continue
|
|
59
|
+
|
|
60
|
+
try:
|
|
61
|
+
# For now, we'll create a placeholder file
|
|
62
|
+
# In the next step, we'll use the MCP tool
|
|
63
|
+
print(f" ✓ Would generate store (type {store_type})")
|
|
64
|
+
results["success"].append({
|
|
65
|
+
"repository": repo_name,
|
|
66
|
+
"store": store_name,
|
|
67
|
+
"type": store_type
|
|
68
|
+
})
|
|
69
|
+
except Exception as e:
|
|
70
|
+
print(f" ❌ ERROR: {str(e)}")
|
|
71
|
+
results["failed"].append({
|
|
72
|
+
"repository": repo_name,
|
|
73
|
+
"error": str(e)
|
|
74
|
+
})
|
|
75
|
+
|
|
76
|
+
# Print summary
|
|
77
|
+
print("\n" + "=" * 80)
|
|
78
|
+
print("SUMMARY")
|
|
79
|
+
print("=" * 80)
|
|
80
|
+
print(f"Total: {len(repositories)}")
|
|
81
|
+
print(f"✓ Success: {len(results['success'])}")
|
|
82
|
+
print(f"⚠️ Skipped: {len(results['skipped'])}")
|
|
83
|
+
print(f"❌ Failed: {len(results['failed'])}")
|
|
84
|
+
|
|
85
|
+
# Save results
|
|
86
|
+
results_file = Path(__file__).parent / 'processing-results.json'
|
|
87
|
+
with open(results_file, 'w') as f:
|
|
88
|
+
json.dump(results, f, indent=2)
|
|
89
|
+
|
|
90
|
+
print(f"\nResults saved to: {results_file}")
|
|
@@ -0,0 +1,356 @@
|
|
|
1
|
+
#!/usr/bin/env node
|
|
2
|
+
/**
|
|
3
|
+
* NH 프로젝트 Store 일괄 재생성 스크립트
|
|
4
|
+
*
|
|
5
|
+
* 1. NH 폴더 하위의 모든 use*Store.ts 파일 찾기
|
|
6
|
+
* 2. Service 이름 추출
|
|
7
|
+
* 3. Repository 찾기
|
|
8
|
+
* 4. MCP 서버로 Store 재생성
|
|
9
|
+
*/
|
|
10
|
+
|
|
11
|
+
const { execSync } = require('child_process');
|
|
12
|
+
const fs = require('fs');
|
|
13
|
+
const path = require('path');
|
|
14
|
+
|
|
15
|
+
const TARGET_DIR = '/Users/kyle/Desktop/nh-fe-bo/src/pages/resources/NH';
|
|
16
|
+
const REPO_BASE = '/Users/kyle/Desktop/nh-fe-bo/src/services/@interface/repository';
|
|
17
|
+
const MCP_SERVER_PATH = '/Users/kyle/work/mcp-axboot/dist/index.js';
|
|
18
|
+
|
|
19
|
+
// ANSI 색상
|
|
20
|
+
const colors = {
|
|
21
|
+
reset: '\x1b[0m',
|
|
22
|
+
bright: '\x1b[1m',
|
|
23
|
+
green: '\x1b[32m',
|
|
24
|
+
yellow: '\x1b[33m',
|
|
25
|
+
red: '\x1b[31m',
|
|
26
|
+
blue: '\x1b[34m',
|
|
27
|
+
cyan: '\x1b[36m',
|
|
28
|
+
};
|
|
29
|
+
|
|
30
|
+
function log(message, color = 'reset') {
|
|
31
|
+
console.log(`${colors[color]}${message}${colors.reset}`);
|
|
32
|
+
}
|
|
33
|
+
|
|
34
|
+
/**
|
|
35
|
+
* Store 파일에서 Service 이름 추출
|
|
36
|
+
* 예: import { Banner, BannerSerevice, ... } from "services"; → BannerSerevice
|
|
37
|
+
*/
|
|
38
|
+
function extractServiceName(storeContent) {
|
|
39
|
+
// "from "services"" 라인 찾기
|
|
40
|
+
const servicesImportMatch = storeContent.match(/import\s*{[^}]*}\s*from\s*['"]services['"];?/);
|
|
41
|
+
if (!servicesImportMatch) return null;
|
|
42
|
+
|
|
43
|
+
const imports = servicesImportMatch[0];
|
|
44
|
+
// Service 이름 패턴: XxxService 또는 XxxSerevice (오타 포함)
|
|
45
|
+
const serviceMatch = imports.match(/([A-Z][a-zA-Z0-9]*)Serevice\b/);
|
|
46
|
+
if (serviceMatch) {
|
|
47
|
+
return serviceMatch[1] + 'Serevice'; // 오타 그대로 사용
|
|
48
|
+
}
|
|
49
|
+
|
|
50
|
+
const serviceMatch2 = imports.match(/([A-Z][a-zA-Z0-9]*)Service\b/);
|
|
51
|
+
return serviceMatch2 ? serviceMatch2[1] + 'Service' : null;
|
|
52
|
+
}
|
|
53
|
+
|
|
54
|
+
/**
|
|
55
|
+
* Service 이름으로 Repository 파일 찾기
|
|
56
|
+
* 예: BannerSerevice → BannerRepository.ts
|
|
57
|
+
*/
|
|
58
|
+
function findRepository(serviceName) {
|
|
59
|
+
// Service 이름에서 Entity 이름 추출
|
|
60
|
+
// BannerSerevice → Banner, MemberService → Member
|
|
61
|
+
const entityMatch = serviceName.match(/([A-Z][a-zA-Z0-9]*)Serevice/)
|
|
62
|
+
|| serviceName.match(/([A-Z][a-zA-Z0-9]*)Service/);
|
|
63
|
+
|
|
64
|
+
if (!entityMatch) return null;
|
|
65
|
+
|
|
66
|
+
const entityName = entityMatch[1];
|
|
67
|
+
|
|
68
|
+
// 가능한 Repository 파일 이름들
|
|
69
|
+
const possibleNames = [
|
|
70
|
+
`${entityName}Repository.ts`,
|
|
71
|
+
`${entityName}Interface.ts`,
|
|
72
|
+
];
|
|
73
|
+
|
|
74
|
+
for (const name of possibleNames) {
|
|
75
|
+
const repoPath = path.join(REPO_BASE, name);
|
|
76
|
+
if (fs.existsSync(repoPath)) {
|
|
77
|
+
return { repoPath, entityName };
|
|
78
|
+
}
|
|
79
|
+
}
|
|
80
|
+
|
|
81
|
+
return null;
|
|
82
|
+
}
|
|
83
|
+
|
|
84
|
+
/**
|
|
85
|
+
* Store 파일에서 패턴 분석으로 Store 타입 결정
|
|
86
|
+
*/
|
|
87
|
+
function detectStoreType(storeContent) {
|
|
88
|
+
const hasList = /callListApi/.test(storeContent);
|
|
89
|
+
const hasDetail = /callDetailApi|setDetail|detail:/.test(storeContent);
|
|
90
|
+
const hasDelete = /callDeleteApi|deleteSpinning/.test(storeContent);
|
|
91
|
+
const hasSave = /callSaveApi|saveSpinning/.test(storeContent);
|
|
92
|
+
const hasExcel = /callExcelDownloadApi|excelSpinning/.test(storeContent);
|
|
93
|
+
const hasModal = /modalOpen|setModalOpen/.test(storeContent);
|
|
94
|
+
const hasTree = /expandedKeys|setExpandedKeys/.test(storeContent);
|
|
95
|
+
const hasDashboard = /summaryData|summarySpinning/.test(storeContent);
|
|
96
|
+
const hasReorder = /setListData.*drag|isReordered/.test(storeContent);
|
|
97
|
+
|
|
98
|
+
// 대시보드
|
|
99
|
+
if (hasDashboard) return 4; // Type 4
|
|
100
|
+
|
|
101
|
+
// 트리
|
|
102
|
+
if (hasTree) return 3; // Type 3
|
|
103
|
+
|
|
104
|
+
// 재정렬
|
|
105
|
+
if (hasReorder && !hasDetail && !hasDelete && !hasSave) return 6; // Type 6
|
|
106
|
+
|
|
107
|
+
// 단순 리스트
|
|
108
|
+
if (!hasDetail && !hasDelete && !hasSave && !hasExcel && !hasModal) return 5; // Type 5
|
|
109
|
+
|
|
110
|
+
// Master-Detail + Excel
|
|
111
|
+
if (hasList && hasExcel && !hasModal && !hasDelete && !hasSave) return 10; // Type 10
|
|
112
|
+
|
|
113
|
+
// List + Selection + Excel
|
|
114
|
+
if (hasList && hasExcel && !hasDetail && !hasModal) return 8; // Type 8
|
|
115
|
+
|
|
116
|
+
// Master-Detail + Modal + Delete + Excel
|
|
117
|
+
if (hasList && hasDetail && hasModal && hasDelete && hasExcel) return 7; // Type 7
|
|
118
|
+
|
|
119
|
+
// Master-Detail + Modal + Detail API
|
|
120
|
+
if (hasList && hasDetail && hasModal && !hasDelete && !hasSave && !hasExcel) return 9; // Type 9
|
|
121
|
+
|
|
122
|
+
// Master-Detail + Modal + Delete (기본)
|
|
123
|
+
if (hasList && hasDetail && hasModal && hasDelete) return 2; // Type 2
|
|
124
|
+
|
|
125
|
+
// 기본 리스트 + 상세
|
|
126
|
+
if (hasList && hasDetail) return 1; // Type 1
|
|
127
|
+
|
|
128
|
+
// 기본값
|
|
129
|
+
return 1;
|
|
130
|
+
}
|
|
131
|
+
|
|
132
|
+
/**
|
|
133
|
+
* MCP 서버로 Store 생성 요청
|
|
134
|
+
*/
|
|
135
|
+
async function generateStore(repoPath, outputPath, storeName, storeType) {
|
|
136
|
+
const { spawn } = require('child_process');
|
|
137
|
+
|
|
138
|
+
return new Promise((resolve, reject) => {
|
|
139
|
+
const MCP_SERVER = spawn('node', [MCP_SERVER_PATH], {
|
|
140
|
+
stdio: ['pipe', 'pipe', 'inherit'],
|
|
141
|
+
});
|
|
142
|
+
|
|
143
|
+
let responseData = '';
|
|
144
|
+
let requestId = 1;
|
|
145
|
+
|
|
146
|
+
MCP_SERVER.stdout.on('data', (data) => {
|
|
147
|
+
responseData += data.toString();
|
|
148
|
+
});
|
|
149
|
+
|
|
150
|
+
MCP_SERVER.on('error', (error) => {
|
|
151
|
+
reject(error);
|
|
152
|
+
});
|
|
153
|
+
|
|
154
|
+
MCP_SERVER.on('close', (code) => {
|
|
155
|
+
try {
|
|
156
|
+
const responses = responseData.trim().split('\n').filter(Boolean);
|
|
157
|
+
for (const response of responses) {
|
|
158
|
+
const parsed = JSON.parse(response);
|
|
159
|
+
if (parsed.result) {
|
|
160
|
+
resolve(parsed.result);
|
|
161
|
+
return;
|
|
162
|
+
}
|
|
163
|
+
}
|
|
164
|
+
reject(new Error('No valid response from MCP server'));
|
|
165
|
+
} catch (e) {
|
|
166
|
+
reject(e);
|
|
167
|
+
}
|
|
168
|
+
});
|
|
169
|
+
|
|
170
|
+
// initialize
|
|
171
|
+
MCP_SERVER.stdin.write(JSON.stringify({
|
|
172
|
+
jsonrpc: '2.0',
|
|
173
|
+
id: requestId++,
|
|
174
|
+
method: 'initialize',
|
|
175
|
+
params: {
|
|
176
|
+
protocolVersion: '2024-11-05',
|
|
177
|
+
capabilities: {},
|
|
178
|
+
clientInfo: {
|
|
179
|
+
name: 'batch-regenerate-script',
|
|
180
|
+
version: '1.0.0',
|
|
181
|
+
},
|
|
182
|
+
},
|
|
183
|
+
}) + '\n');
|
|
184
|
+
|
|
185
|
+
// tools/list
|
|
186
|
+
MCP_SERVER.stdin.write(JSON.stringify({
|
|
187
|
+
jsonrpc: '2.0',
|
|
188
|
+
id: requestId++,
|
|
189
|
+
method: 'tools/list',
|
|
190
|
+
}) + '\n');
|
|
191
|
+
|
|
192
|
+
// generate_store 호출
|
|
193
|
+
setTimeout(() => {
|
|
194
|
+
MCP_SERVER.stdin.write(JSON.stringify({
|
|
195
|
+
jsonrpc: '2.0',
|
|
196
|
+
id: requestId++,
|
|
197
|
+
method: 'tools/call',
|
|
198
|
+
params: {
|
|
199
|
+
name: 'generate_store',
|
|
200
|
+
arguments: {
|
|
201
|
+
interfacePath: repoPath,
|
|
202
|
+
outputPath: outputPath,
|
|
203
|
+
storeName: storeName,
|
|
204
|
+
storeType: storeType,
|
|
205
|
+
},
|
|
206
|
+
},
|
|
207
|
+
}) + '\n');
|
|
208
|
+
}, 100);
|
|
209
|
+
|
|
210
|
+
setTimeout(() => {
|
|
211
|
+
MCP_SERVER.kill();
|
|
212
|
+
}, 10000);
|
|
213
|
+
});
|
|
214
|
+
}
|
|
215
|
+
|
|
216
|
+
/**
|
|
217
|
+
* 메인 처리 함수
|
|
218
|
+
*/
|
|
219
|
+
async function processStore(storePath) {
|
|
220
|
+
const content = fs.readFileSync(storePath, 'utf8');
|
|
221
|
+
|
|
222
|
+
// Service 이름 추출
|
|
223
|
+
const serviceName = extractServiceName(content);
|
|
224
|
+
if (!serviceName) {
|
|
225
|
+
log(` ⚠️ Service를 찾을 수 없음`, 'yellow');
|
|
226
|
+
return { success: false, reason: 'No service found' };
|
|
227
|
+
}
|
|
228
|
+
|
|
229
|
+
// Repository 찾기
|
|
230
|
+
const repoInfo = findRepository(serviceName);
|
|
231
|
+
if (!repoInfo) {
|
|
232
|
+
log(` ⚠️ Repository를 찾을 수 없음 (${serviceName})`, 'yellow');
|
|
233
|
+
return { success: false, reason: 'No repository found', serviceName };
|
|
234
|
+
}
|
|
235
|
+
|
|
236
|
+
const { repoPath, entityName } = repoInfo;
|
|
237
|
+
|
|
238
|
+
// 새 Store 이름 생성
|
|
239
|
+
const storeDir = path.dirname(storePath);
|
|
240
|
+
const originalName = path.basename(storePath, '.ts');
|
|
241
|
+
const newStoreName = originalName.replace('use', 'use').replace('Store', 'StoreNew');
|
|
242
|
+
const outputPath = path.join(storeDir, `${newStoreName}.ts`);
|
|
243
|
+
|
|
244
|
+
// Store 타입 결정
|
|
245
|
+
const storeType = detectStoreType(content);
|
|
246
|
+
|
|
247
|
+
log(` 📦 Entity: ${entityName}`, 'cyan');
|
|
248
|
+
log(` 📝 Repository: ${path.basename(repoPath)}`, 'cyan');
|
|
249
|
+
log(` 🔧 Store Type: ${storeType}`, 'cyan');
|
|
250
|
+
log(` 📄 Output: ${newStoreName}.ts`, 'cyan');
|
|
251
|
+
|
|
252
|
+
try {
|
|
253
|
+
await generateStore(repoPath, outputPath, newStoreName, storeType);
|
|
254
|
+
log(` ✅ 생성 완료`, 'green');
|
|
255
|
+
return {
|
|
256
|
+
success: true,
|
|
257
|
+
entityName,
|
|
258
|
+
serviceName,
|
|
259
|
+
originalName,
|
|
260
|
+
newName: newStoreName,
|
|
261
|
+
storeType,
|
|
262
|
+
};
|
|
263
|
+
} catch (error) {
|
|
264
|
+
log(` ❌ 생성 실패: ${error.message}`, 'red');
|
|
265
|
+
return {
|
|
266
|
+
success: false,
|
|
267
|
+
reason: 'Generation failed',
|
|
268
|
+
error: error.message,
|
|
269
|
+
};
|
|
270
|
+
}
|
|
271
|
+
}
|
|
272
|
+
|
|
273
|
+
/**
|
|
274
|
+
* 메인 실행
|
|
275
|
+
*/
|
|
276
|
+
async function main() {
|
|
277
|
+
log('='.repeat(60), 'cyan');
|
|
278
|
+
log('NH 프로젝트 Store 일괄 재생성', 'bright');
|
|
279
|
+
log('='.repeat(60), 'cyan');
|
|
280
|
+
|
|
281
|
+
// MCP 서버 확인
|
|
282
|
+
if (!fs.existsSync(MCP_SERVER_PATH)) {
|
|
283
|
+
log(`❌ MCP 서버를 찾을 수 없음: ${MCP_SERVER_PATH}`, 'red');
|
|
284
|
+
log('먼저 npm run build를 실행해주세요.', 'yellow');
|
|
285
|
+
process.exit(1);
|
|
286
|
+
}
|
|
287
|
+
|
|
288
|
+
// 모든 Store 파일 찾기
|
|
289
|
+
const findCommand = `find "${TARGET_DIR}" -name "use*Store.ts" -type f`;
|
|
290
|
+
let storeFiles;
|
|
291
|
+
try {
|
|
292
|
+
storeFiles = execSync(findCommand, { encoding: 'utf8' }).trim().split('\n');
|
|
293
|
+
} catch (error) {
|
|
294
|
+
log(`❌ Store 파일을 찾을 수 없음: ${TARGET_DIR}`, 'red');
|
|
295
|
+
process.exit(1);
|
|
296
|
+
}
|
|
297
|
+
|
|
298
|
+
log(`\n📊 총 ${storeFiles.length}개의 Store 파일 발견\n`, 'bright');
|
|
299
|
+
|
|
300
|
+
const results = {
|
|
301
|
+
total: storeFiles.length,
|
|
302
|
+
success: 0,
|
|
303
|
+
failed: 0,
|
|
304
|
+
noService: 0,
|
|
305
|
+
noRepo: 0,
|
|
306
|
+
details: [],
|
|
307
|
+
};
|
|
308
|
+
|
|
309
|
+
// 각 Store 처리
|
|
310
|
+
for (let i = 0; i < storeFiles.length; i++) {
|
|
311
|
+
const storePath = storeFiles[i];
|
|
312
|
+
const relativePath = path.relative(TARGET_DIR, storePath);
|
|
313
|
+
|
|
314
|
+
process.stdout.write(`\r[${i + 1}/${storeFiles.length}] ${relativePath.padEnd(50)} `);
|
|
315
|
+
|
|
316
|
+
const result = await processStore(storePath);
|
|
317
|
+
|
|
318
|
+
results.details.push({ ...result, path: relativePath });
|
|
319
|
+
|
|
320
|
+
if (result.success) {
|
|
321
|
+
results.success++;
|
|
322
|
+
} else {
|
|
323
|
+
results.failed++;
|
|
324
|
+
if (result.reason === 'No service found') results.noService++;
|
|
325
|
+
if (result.reason === 'No repository found') results.noRepo++;
|
|
326
|
+
}
|
|
327
|
+
}
|
|
328
|
+
|
|
329
|
+
// 결과 요약
|
|
330
|
+
log('\n');
|
|
331
|
+
log('='.repeat(60), 'cyan');
|
|
332
|
+
log('📊 결과 요약', 'bright');
|
|
333
|
+
log('='.repeat(60), 'cyan');
|
|
334
|
+
log(` 전체: ${results.total}`, 'cyan');
|
|
335
|
+
log(` 성공: ${results.success}`, 'green');
|
|
336
|
+
log(` 실패: ${results.failed}`, 'red');
|
|
337
|
+
log(` - Service 없음: ${results.noService}`, 'yellow');
|
|
338
|
+
log(` - Repository 없음: ${results.noRepo}`, 'yellow');
|
|
339
|
+
log('='.repeat(60), 'cyan');
|
|
340
|
+
|
|
341
|
+
// 실패 목록
|
|
342
|
+
if (results.details.filter(r => !r.success && r.reason !== 'No service found' && r.reason !== 'No repository found').length > 0) {
|
|
343
|
+
log('\n⚠️ 생성 실패 목록:', 'yellow');
|
|
344
|
+
results.details
|
|
345
|
+
.filter(r => !r.success && r.reason !== 'No service found' && r.reason !== 'No repository found')
|
|
346
|
+
.forEach(r => {
|
|
347
|
+
log(` ❌ ${r.path}: ${r.error || r.reason}`, 'red');
|
|
348
|
+
});
|
|
349
|
+
}
|
|
350
|
+
}
|
|
351
|
+
|
|
352
|
+
main().catch(error => {
|
|
353
|
+
log(`\n❌ 치명적 오류: ${error.message}`, 'red');
|
|
354
|
+
console.error(error);
|
|
355
|
+
process.exit(1);
|
|
356
|
+
});
|
|
@@ -0,0 +1,227 @@
|
|
|
1
|
+
#!/usr/bin/env node
|
|
2
|
+
|
|
3
|
+
// Direct import of handlers to avoid MCP protocol overhead
|
|
4
|
+
import { generateStore } from '../dist/handlers/generate.js';
|
|
5
|
+
import fs from 'fs';
|
|
6
|
+
import path from 'path';
|
|
7
|
+
|
|
8
|
+
const SOURCE_DIR = '/Users/kyle/Desktop/nh-fe-bo/src/services/@interface/repository';
|
|
9
|
+
const OUTPUT_DIR = '/Users/kyle/Desktop/nh-fe-bo/src/pages/resources/NH/test';
|
|
10
|
+
|
|
11
|
+
// Store type detection patterns
|
|
12
|
+
function detectStoreType(filePath) {
|
|
13
|
+
const content = fs.readFileSync(filePath, 'utf-8');
|
|
14
|
+
const methods = content.match(/async\s+(\w+)\s*\(/g) || [];
|
|
15
|
+
const methodNames = methods.map(m => m.match(/async\s+(\w+)/)[1].toLowerCase());
|
|
16
|
+
|
|
17
|
+
const hasList = methodNames.some(m => m.includes('list'));
|
|
18
|
+
const hasDetail = methodNames.some(m => m.includes('detail') || m.includes('dtl'));
|
|
19
|
+
const hasSave = methodNames.some(m => m.includes('save'));
|
|
20
|
+
const hasDelete = methodNames.some(m => m.includes('delete') || m.includes('del'));
|
|
21
|
+
const hasExcel = methodNames.some(m => m.includes('excel') || m.includes('download') || m.includes('export'));
|
|
22
|
+
|
|
23
|
+
if (hasList && hasDetail && hasSave && hasDelete && hasExcel) {
|
|
24
|
+
return 7;
|
|
25
|
+
} else if (hasList && hasDetail && hasSave && hasDelete) {
|
|
26
|
+
return 2;
|
|
27
|
+
} else if (hasList && hasSave) {
|
|
28
|
+
return 1;
|
|
29
|
+
} else {
|
|
30
|
+
return 5;
|
|
31
|
+
}
|
|
32
|
+
}
|
|
33
|
+
|
|
34
|
+
// Get all repository files
|
|
35
|
+
function getRepositoryFiles() {
|
|
36
|
+
const files = fs.readdirSync(SOURCE_DIR)
|
|
37
|
+
.filter(file => file.endsWith('Repository.ts'));
|
|
38
|
+
return files.map(file => path.join(SOURCE_DIR, file));
|
|
39
|
+
}
|
|
40
|
+
|
|
41
|
+
// Extract repository name
|
|
42
|
+
function getRepositoryName(filePath) {
|
|
43
|
+
return path.basename(filePath, 'Repository.ts');
|
|
44
|
+
}
|
|
45
|
+
|
|
46
|
+
// Generate store name
|
|
47
|
+
function getStoreName(repositoryName) {
|
|
48
|
+
return `use${repositoryName}ListStore`;
|
|
49
|
+
}
|
|
50
|
+
|
|
51
|
+
// Get output path
|
|
52
|
+
function getOutputPath(repositoryName) {
|
|
53
|
+
return path.join(OUTPUT_DIR, `use${repositoryName}ListStore.ts`);
|
|
54
|
+
}
|
|
55
|
+
|
|
56
|
+
// Main execution
|
|
57
|
+
async function main() {
|
|
58
|
+
console.log('='.repeat(80));
|
|
59
|
+
console.log('Batch Store Generation for NH-FE-BO Repository Files');
|
|
60
|
+
console.log('='.repeat(80));
|
|
61
|
+
console.log(`Source Directory: ${SOURCE_DIR}`);
|
|
62
|
+
console.log(`Output Directory: ${OUTPUT_DIR}`);
|
|
63
|
+
console.log('');
|
|
64
|
+
|
|
65
|
+
const files = getRepositoryFiles();
|
|
66
|
+
console.log(`Found ${files.length} repository files\n`);
|
|
67
|
+
|
|
68
|
+
const results = {
|
|
69
|
+
total: files.length,
|
|
70
|
+
success: 0,
|
|
71
|
+
failed: 0,
|
|
72
|
+
skipped: 0,
|
|
73
|
+
generated: [],
|
|
74
|
+
failedFiles: [],
|
|
75
|
+
typeDistribution: {}
|
|
76
|
+
};
|
|
77
|
+
|
|
78
|
+
// Process in batches of 10
|
|
79
|
+
const batchSize = 10;
|
|
80
|
+
for (let i = 0; i < files.length; i += batchSize) {
|
|
81
|
+
const batch = files.slice(i, i + batchSize);
|
|
82
|
+
console.log(`\nProcessing batch ${Math.floor(i/batchSize) + 1}/${Math.ceil(files.length/batchSize)} (files ${i + 1}-${Math.min(i + batchSize, files.length)})`);
|
|
83
|
+
console.log('-'.repeat(80));
|
|
84
|
+
|
|
85
|
+
for (const filePath of batch) {
|
|
86
|
+
const index = files.indexOf(filePath) + 1;
|
|
87
|
+
const repositoryName = getRepositoryName(filePath);
|
|
88
|
+
const storeName = getStoreName(repositoryName);
|
|
89
|
+
const outputPath = getOutputPath(repositoryName);
|
|
90
|
+
const storeType = detectStoreType(filePath);
|
|
91
|
+
|
|
92
|
+
// Track type distribution
|
|
93
|
+
results.typeDistribution[storeType] = (results.typeDistribution[storeType] || 0) + 1;
|
|
94
|
+
|
|
95
|
+
console.log(`[${index}/${files.length}] ${repositoryName}Repository → Type ${storeType}`);
|
|
96
|
+
|
|
97
|
+
try {
|
|
98
|
+
// Check if file already exists
|
|
99
|
+
if (fs.existsSync(outputPath)) {
|
|
100
|
+
console.log(` ⚠️ Already exists, skipping\n`);
|
|
101
|
+
results.skipped++;
|
|
102
|
+
results.generated.push({
|
|
103
|
+
repositoryName,
|
|
104
|
+
storeName,
|
|
105
|
+
storeType,
|
|
106
|
+
status: 'exists',
|
|
107
|
+
outputPath
|
|
108
|
+
});
|
|
109
|
+
continue;
|
|
110
|
+
}
|
|
111
|
+
|
|
112
|
+
// Generate store using direct handler call
|
|
113
|
+
console.log(` 🔄 Generating store...`);
|
|
114
|
+
const result = await generateStore({
|
|
115
|
+
interfacePath: filePath,
|
|
116
|
+
outputPath: outputPath,
|
|
117
|
+
storeType: storeType,
|
|
118
|
+
storeName: storeName
|
|
119
|
+
});
|
|
120
|
+
|
|
121
|
+
// Parse result
|
|
122
|
+
let resultData;
|
|
123
|
+
if (result.content && result.content[0]) {
|
|
124
|
+
resultData = JSON.parse(result.content[0].text);
|
|
125
|
+
} else {
|
|
126
|
+
throw new Error('Invalid response format');
|
|
127
|
+
}
|
|
128
|
+
|
|
129
|
+
if (!resultData.success) {
|
|
130
|
+
throw new Error(resultData.error || 'Unknown error');
|
|
131
|
+
}
|
|
132
|
+
|
|
133
|
+
// Check if file was created
|
|
134
|
+
if (fs.existsSync(outputPath)) {
|
|
135
|
+
results.success++;
|
|
136
|
+
results.generated.push({
|
|
137
|
+
repositoryName,
|
|
138
|
+
storeName,
|
|
139
|
+
storeType,
|
|
140
|
+
status: 'success',
|
|
141
|
+
outputPath
|
|
142
|
+
});
|
|
143
|
+
console.log(` ✓ Generated\n`);
|
|
144
|
+
} else {
|
|
145
|
+
throw new Error('File was not created');
|
|
146
|
+
}
|
|
147
|
+
|
|
148
|
+
} catch (error) {
|
|
149
|
+
results.failed++;
|
|
150
|
+
results.failedFiles.push({
|
|
151
|
+
repositoryName,
|
|
152
|
+
storeName,
|
|
153
|
+
error: error.message
|
|
154
|
+
});
|
|
155
|
+
console.log(` ✗ Failed: ${error.message}\n`);
|
|
156
|
+
}
|
|
157
|
+
|
|
158
|
+
// Small delay
|
|
159
|
+
await new Promise(resolve => setTimeout(resolve, 50));
|
|
160
|
+
}
|
|
161
|
+
}
|
|
162
|
+
|
|
163
|
+
// Print summary
|
|
164
|
+
console.log('\n' + '='.repeat(80));
|
|
165
|
+
console.log('SUMMARY');
|
|
166
|
+
console.log('='.repeat(80));
|
|
167
|
+
console.log(`Total files processed: ${results.total}`);
|
|
168
|
+
console.log(`Successfully generated: ${results.success}`);
|
|
169
|
+
console.log(`Skipped (already exists): ${results.skipped}`);
|
|
170
|
+
console.log(`Failed: ${results.failed}`);
|
|
171
|
+
console.log('');
|
|
172
|
+
console.log('Type Distribution:');
|
|
173
|
+
Object.entries(results.typeDistribution).sort((a, b) => a[0] - b[0]).forEach(([type, count]) => {
|
|
174
|
+
console.log(` Type ${type}: ${count} files`);
|
|
175
|
+
});
|
|
176
|
+
|
|
177
|
+
// Save detailed results
|
|
178
|
+
const resultsPath = path.join(OUTPUT_DIR, 'generation-results.json');
|
|
179
|
+
fs.writeFileSync(resultsPath, JSON.stringify(results, null, 2));
|
|
180
|
+
console.log(`\nDetailed results saved to: ${resultsPath}`);
|
|
181
|
+
|
|
182
|
+
// Generate markdown report
|
|
183
|
+
const markdownPath = path.join(OUTPUT_DIR, 'generation-results.md');
|
|
184
|
+
const markdown = generateMarkdownReport(results);
|
|
185
|
+
fs.writeFileSync(markdownPath, markdown);
|
|
186
|
+
console.log(`Markdown report saved to: ${markdownPath}`);
|
|
187
|
+
}
|
|
188
|
+
|
|
189
|
+
function generateMarkdownReport(results) {
|
|
190
|
+
const date = new Date().toISOString();
|
|
191
|
+
|
|
192
|
+
let md = `# Store Generation Results\n\n`;
|
|
193
|
+
md += `**Generated:** ${date}\n\n`;
|
|
194
|
+
md += `## Summary\n\n`;
|
|
195
|
+
md += `- **Total Files:** ${results.total}\n`;
|
|
196
|
+
md += `- **Successfully Generated:** ${results.success}\n`;
|
|
197
|
+
md += `- **Skipped (already exists):** ${results.skipped}\n`;
|
|
198
|
+
md += `- **Failed:** ${results.failed}\n\n`;
|
|
199
|
+
|
|
200
|
+
md += `## Type Distribution\n\n`;
|
|
201
|
+
md += `| Type | Count |\n`;
|
|
202
|
+
md += `|------|-------|\n`;
|
|
203
|
+
Object.entries(results.typeDistribution).sort((a, b) => a[0] - b[0]).forEach(([type, count]) => {
|
|
204
|
+
md += `| ${type} | ${count} |\n`;
|
|
205
|
+
});
|
|
206
|
+
|
|
207
|
+
md += `\n## Generated Stores\n\n`;
|
|
208
|
+
md += `| # | Repository | Store Name | Type | Status |\n`;
|
|
209
|
+
md += `|---|------------|------------|------|--------|\n`;
|
|
210
|
+
results.generated.forEach((item, idx) => {
|
|
211
|
+
const status = item.status === 'success' ? '✓' : '⚠️';
|
|
212
|
+
md += `| ${idx + 1} | ${item.repositoryName}Repository | ${item.storeName} | ${item.storeType} | ${status} |\n`;
|
|
213
|
+
});
|
|
214
|
+
|
|
215
|
+
if (results.failedFiles.length > 0) {
|
|
216
|
+
md += `\n## Failed Files\n\n`;
|
|
217
|
+
md += `| Repository | Error |\n`;
|
|
218
|
+
md += `|------------|-------|\n`;
|
|
219
|
+
results.failedFiles.forEach(item => {
|
|
220
|
+
md += `| ${item.repositoryName} | ${item.error} |\n`;
|
|
221
|
+
});
|
|
222
|
+
}
|
|
223
|
+
|
|
224
|
+
return md;
|
|
225
|
+
}
|
|
226
|
+
|
|
227
|
+
main().catch(console.error);
|