@ncds/ui-admin-mcp 1.0.0-alpha.25 → 1.0.0-alpha.26
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/bin/components.bundle.js +8 -8
- package/bin/definitions/external/editor.js +1 -53
- package/bin/definitions/external/step-guide.js +1 -52
- package/bin/server.js +1 -231
- package/bin/tools/external/editor.js +1 -88
- package/bin/tools/external/step-guide.js +1 -79
- package/bin/tools/getComponentProps.js +1 -26
- package/bin/tools/getDesignTokens.js +1 -20
- package/bin/tools/listComponents.js +1 -24
- package/bin/tools/listCompositionOverrides.js +1 -156
- package/bin/tools/listIcons.js +1 -23
- package/bin/tools/ping.js +1 -20
- package/bin/tools/renderToHtml.js +1 -882
- package/bin/tools/searchComponent.js +1 -33
- package/bin/tools/searchIcon.js +1 -19
- package/bin/tools/validateHtml.js +1 -153
- package/bin/types.js +1 -5
- package/bin/utils/bemValidator.js +1 -210
- package/bin/utils/compliance.js +1 -203
- package/bin/utils/dataLoader.js +1 -277
- package/bin/utils/domEnvironment.js +1 -77
- package/bin/utils/fuzzyMatch.js +1 -120
- package/bin/utils/logger.js +1 -27
- package/bin/utils/response.js +1 -39
- package/bin/utils/tokenValidator.js +1 -194
- package/bin/version.d.ts +1 -1
- package/bin/version.js +1 -9
- package/data/select-box.json +6 -0
- package/data/select.json +6 -0
- package/package.json +5 -2
package/bin/utils/dataLoader.js
CHANGED
|
@@ -1,277 +1 @@
|
|
|
1
|
-
"use strict";
|
|
2
|
-
var __importDefault = (this && this.__importDefault) || function (mod) {
|
|
3
|
-
return (mod && mod.__esModule) ? mod : { "default": mod };
|
|
4
|
-
};
|
|
5
|
-
Object.defineProperty(exports, "__esModule", { value: true });
|
|
6
|
-
exports.loadTokenData = exports.loadMeta = exports.loadJsApi = exports.loadInstructions = exports.loadIconData = exports.loadComponentsFromDir = exports.loadCompositionOverrides = exports.loadComplianceRules = exports.getComponent = exports.getAllComponents = exports.DEFAULT_DATA_DIR = void 0;
|
|
7
|
-
/**
|
|
8
|
-
* dataLoader — mcp/data/*.json + definitions/ 파일을 읽어 반환
|
|
9
|
-
*
|
|
10
|
-
* 함수 분류:
|
|
11
|
-
* loadComponentsFromDir, loadCompositionOverrides, loadIconData, loadCdnMeta, loadInstructions → 부수효과 (fs I/O)
|
|
12
|
-
* getComponent, getAllComponents → 순수 함수 (Map 조회)
|
|
13
|
-
*
|
|
14
|
-
* Map + 메타데이터 소유권은 server.ts가 가진다. 이 파일은 상태를 보유하지 않는다.
|
|
15
|
-
*/
|
|
16
|
-
const fs_1 = __importDefault(require("fs"));
|
|
17
|
-
const path_1 = __importDefault(require("path"));
|
|
18
|
-
const logger_js_1 = require("./logger.js");
|
|
19
|
-
const response_js_1 = require("./response.js");
|
|
20
|
-
const DEFAULT_DATA_DIR = path_1.default.resolve(__dirname, '../../data');
|
|
21
|
-
exports.DEFAULT_DATA_DIR = DEFAULT_DATA_DIR;
|
|
22
|
-
/**
|
|
23
|
-
* P8: Composition Overrides 위치.
|
|
24
|
-
* extract-mcp-data.ts 가 만드는 data/*.json 위에 컴포넌트 디렉토리를 건드리지 않고
|
|
25
|
-
* allowedChildren / allowedParents / canonicalExample / canonicalExamples / bemClassesExtra 를
|
|
26
|
-
* 추가하는 단일 채널.
|
|
27
|
-
*
|
|
28
|
-
* 빌드 산출물(bin/) 에서는 src/overrides 가 build:server 단계에서 복사되지 않을 수 있으므로
|
|
29
|
-
* src 와 bin 양쪽을 fallback 으로 본다.
|
|
30
|
-
*/
|
|
31
|
-
const COMPOSITION_OVERRIDES_CANDIDATES = [
|
|
32
|
-
path_1.default.resolve(__dirname, '../../src/overrides/composition.json'),
|
|
33
|
-
path_1.default.resolve(__dirname, '../overrides/composition.json'),
|
|
34
|
-
];
|
|
35
|
-
/** composition.json 파일에서 메타 키를 제거하여 CompositionEntry 객체만 수집 */
|
|
36
|
-
const filterCompositionEntries = (parsed) => {
|
|
37
|
-
if (typeof parsed !== 'object' || !parsed)
|
|
38
|
-
return {};
|
|
39
|
-
const entries = {};
|
|
40
|
-
for (const [key, value] of Object.entries(parsed)) {
|
|
41
|
-
if (key.startsWith('_'))
|
|
42
|
-
continue;
|
|
43
|
-
if (value && typeof value === 'object' && !Array.isArray(value))
|
|
44
|
-
entries[key] = value;
|
|
45
|
-
}
|
|
46
|
-
return entries;
|
|
47
|
-
};
|
|
48
|
-
const loadCompositionOverrides = () => {
|
|
49
|
-
for (const candidate of COMPOSITION_OVERRIDES_CANDIDATES) {
|
|
50
|
-
try {
|
|
51
|
-
const raw = fs_1.default.readFileSync(candidate, 'utf-8');
|
|
52
|
-
const parsed = JSON.parse(raw);
|
|
53
|
-
const entries = filterCompositionEntries(parsed);
|
|
54
|
-
logger_js_1.logger.info(`composition overrides 로딩: ${Object.keys(entries).length}개 (${path_1.default.basename(candidate)})`);
|
|
55
|
-
return entries;
|
|
56
|
-
}
|
|
57
|
-
catch (err) {
|
|
58
|
-
// ENOENT: 파일 없으면 다음 후보 시도 (TOCTOU 회피)
|
|
59
|
-
if (err instanceof Error && 'code' in err && err.code === 'ENOENT') {
|
|
60
|
-
continue;
|
|
61
|
-
}
|
|
62
|
-
logger_js_1.logger.warn(`composition overrides 파싱 실패 (${candidate}): ${(0, response_js_1.toErrorMessage)(err)}`);
|
|
63
|
-
return {};
|
|
64
|
-
}
|
|
65
|
-
}
|
|
66
|
-
return {};
|
|
67
|
-
};
|
|
68
|
-
exports.loadCompositionOverrides = loadCompositionOverrides;
|
|
69
|
-
/** P8: `_` 접두 메타 키를 객체에서 제거 (얕은 필터). canonicalExamples 같은 중첩 맵에서 _note 누출 방지. */
|
|
70
|
-
const stripMetaKeys = (obj) => {
|
|
71
|
-
if (!obj)
|
|
72
|
-
return obj;
|
|
73
|
-
const cleaned = {};
|
|
74
|
-
for (const [key, value] of Object.entries(obj)) {
|
|
75
|
-
if (key.startsWith('_'))
|
|
76
|
-
continue;
|
|
77
|
-
cleaned[key] = value;
|
|
78
|
-
}
|
|
79
|
-
return cleaned;
|
|
80
|
-
};
|
|
81
|
-
/** ComponentData 에 composition overrides 항목을 in-place 병합 — bemClasses 는 union, 나머지는 추가 필드. */
|
|
82
|
-
const mergeCompositionEntry = (component, entry) => {
|
|
83
|
-
if (entry.bemClassesExtra && entry.bemClassesExtra.length > 0) {
|
|
84
|
-
const merged = new Set([...component.bemClasses, ...entry.bemClassesExtra]);
|
|
85
|
-
component.bemClasses = [...merged].sort();
|
|
86
|
-
}
|
|
87
|
-
if (entry.allowedChildren)
|
|
88
|
-
component.allowedChildren = entry.allowedChildren;
|
|
89
|
-
if (entry.allowedParents)
|
|
90
|
-
component.allowedParents = entry.allowedParents;
|
|
91
|
-
if (entry.canonicalExample) {
|
|
92
|
-
// canonicalExample 내부 _note / _comment 같은 메타 키 제거 — 응답에 누출되지 않도록
|
|
93
|
-
component.canonicalExample = stripMetaKeys(entry.canonicalExample);
|
|
94
|
-
}
|
|
95
|
-
if (entry.canonicalExamples) {
|
|
96
|
-
// canonicalExamples 내부 _note / _comment 같은 메타 키 제거 — 응답에 누출되지 않도록
|
|
97
|
-
component.canonicalExamples = stripMetaKeys(entry.canonicalExamples);
|
|
98
|
-
}
|
|
99
|
-
// P13: description 끝에 append — list_components 응답에서 컴포넌트 용도 명시
|
|
100
|
-
if (entry.descriptionExtra) {
|
|
101
|
-
component.description = `${component.description}\n\n${entry.descriptionExtra}`;
|
|
102
|
-
}
|
|
103
|
-
// P13: aliases union — search_component 별칭 보강 (중복 제거)
|
|
104
|
-
if (entry.aliasesExtra && entry.aliasesExtra.length > 0) {
|
|
105
|
-
const set = new Set([...(component.aliases ?? []), ...entry.aliasesExtra]);
|
|
106
|
-
component.aliases = [...set];
|
|
107
|
-
}
|
|
108
|
-
};
|
|
109
|
-
// ── 검증 헬퍼 (§ 3.8 복잡한 조건을 의도가 드러나는 이름으로 분리) ──────────
|
|
110
|
-
/** JSON.parse 결과가 name: string 필드를 가진 컴포넌트 객체인지 검증 */
|
|
111
|
-
const isValidComponentJson = (data) => !!data && typeof data === 'object' && 'name' in data && typeof data.name === 'string';
|
|
112
|
-
/** JSON.parse 결과가 icons 배열을 가진 아이콘 데이터인지 검증 */
|
|
113
|
-
const isValidIconJson = (data) => !!data && typeof data === 'object' && 'icons' in data && Array.isArray(data.icons);
|
|
114
|
-
// ── 부수효과 함수 (fs I/O) ────────────────────────────────────────────────
|
|
115
|
-
/** data/ 디렉토리의 JSON 파일을 읽어 componentMap + compositionOverrides로 반환 */
|
|
116
|
-
const loadComponentsFromDir = (dataDir) => {
|
|
117
|
-
if (!fs_1.default.existsSync(dataDir)) {
|
|
118
|
-
throw new Error(`mcp/data/ 디렉토리가 없습니다: ${dataDir}`);
|
|
119
|
-
}
|
|
120
|
-
const jsonFiles = fs_1.default.readdirSync(dataDir).filter((f) => f.endsWith('.json') && !f.startsWith('_'));
|
|
121
|
-
if (jsonFiles.length === 0) {
|
|
122
|
-
throw new Error(`mcp/data/ 디렉토리에 JSON 파일이 없습니다: ${dataDir}`);
|
|
123
|
-
}
|
|
124
|
-
const map = new Map();
|
|
125
|
-
// P8: composition overrides (composition.json) 를 한 번만 로딩해 각 컴포넌트에 병합 — 호출자에게도 반환
|
|
126
|
-
const compositionOverrides = loadCompositionOverrides();
|
|
127
|
-
for (const file of jsonFiles) {
|
|
128
|
-
const filePath = path_1.default.join(dataDir, file);
|
|
129
|
-
const raw = fs_1.default.readFileSync(filePath, 'utf-8');
|
|
130
|
-
const parsed = JSON.parse(raw);
|
|
131
|
-
if (!isValidComponentJson(parsed)) {
|
|
132
|
-
throw new Error(`JSON 파싱 실패 (${file}): name 필드가 없거나 올바르지 않습니다`);
|
|
133
|
-
}
|
|
134
|
-
const component = parsed;
|
|
135
|
-
const compositionOverridesEntry = compositionOverrides[component.name];
|
|
136
|
-
if (compositionOverridesEntry)
|
|
137
|
-
mergeCompositionEntry(component, compositionOverridesEntry);
|
|
138
|
-
map.set(component.name, component);
|
|
139
|
-
}
|
|
140
|
-
logger_js_1.logger.info(`컴포넌트 ${map.size}개 로딩 완료`);
|
|
141
|
-
return { map, compositionOverrides };
|
|
142
|
-
};
|
|
143
|
-
exports.loadComponentsFromDir = loadComponentsFromDir;
|
|
144
|
-
/** data/_icons.json을 읽어 IconData로 반환 (파일 없으면 빈 데이터) */
|
|
145
|
-
const loadIconData = (dataDir) => {
|
|
146
|
-
const iconPath = path_1.default.join(dataDir, '_icons.json');
|
|
147
|
-
if (!fs_1.default.existsSync(iconPath)) {
|
|
148
|
-
logger_js_1.logger.error(`_icons.json이 없습니다: ${iconPath}`);
|
|
149
|
-
return { totalCount: 0, fillCount: 0, icons: [] };
|
|
150
|
-
}
|
|
151
|
-
try {
|
|
152
|
-
const raw = fs_1.default.readFileSync(iconPath, 'utf-8');
|
|
153
|
-
const parsed = JSON.parse(raw);
|
|
154
|
-
if (!isValidIconJson(parsed)) {
|
|
155
|
-
throw new Error('icons 배열이 없습니다');
|
|
156
|
-
}
|
|
157
|
-
return parsed;
|
|
158
|
-
}
|
|
159
|
-
catch (err) {
|
|
160
|
-
logger_js_1.logger.error(`_icons.json 파싱 실패: ${(0, response_js_1.toErrorMessage)(err)}`);
|
|
161
|
-
return { totalCount: 0, fillCount: 0, icons: [] };
|
|
162
|
-
}
|
|
163
|
-
};
|
|
164
|
-
exports.loadIconData = loadIconData;
|
|
165
|
-
/** data/_meta.json에서 CDN + icon 메타를 1회 읽기로 로드 */
|
|
166
|
-
const loadMeta = (dataDir) => {
|
|
167
|
-
const metaPath = path_1.default.join(dataDir, '_meta.json');
|
|
168
|
-
if (!fs_1.default.existsSync(metaPath))
|
|
169
|
-
return { cdn: null, icon: null };
|
|
170
|
-
try {
|
|
171
|
-
const raw = fs_1.default.readFileSync(metaPath, 'utf-8');
|
|
172
|
-
const meta = JSON.parse(raw);
|
|
173
|
-
return {
|
|
174
|
-
cdn: isValidCdnMeta(meta.cdn) ? meta.cdn : null,
|
|
175
|
-
icon: isValidIconMeta(meta.icon) ? meta.icon : null,
|
|
176
|
-
};
|
|
177
|
-
}
|
|
178
|
-
catch (err) {
|
|
179
|
-
logger_js_1.logger.error(`_meta.json 파싱 실패: ${(0, response_js_1.toErrorMessage)(err)}`);
|
|
180
|
-
return { cdn: null, icon: null };
|
|
181
|
-
}
|
|
182
|
-
};
|
|
183
|
-
exports.loadMeta = loadMeta;
|
|
184
|
-
/** CdnMeta 스키마 검증 */
|
|
185
|
-
const isValidCdnMeta = (data) => !!data &&
|
|
186
|
-
typeof data === 'object' &&
|
|
187
|
-
'version' in data &&
|
|
188
|
-
'css' in data &&
|
|
189
|
-
'js' in data &&
|
|
190
|
-
typeof data.version === 'string';
|
|
191
|
-
/** IconMeta 스키마 검증 */
|
|
192
|
-
const isValidIconMeta = (data) => !!data &&
|
|
193
|
-
typeof data === 'object' &&
|
|
194
|
-
'packageName' in data &&
|
|
195
|
-
'version' in data &&
|
|
196
|
-
typeof data.packageName === 'string';
|
|
197
|
-
/** data/_tokens.json을 읽어 TokenData로 반환 (파일 없으면 빈 데이터) */
|
|
198
|
-
const loadTokenData = (dataDir) => {
|
|
199
|
-
const tokenPath = path_1.default.join(dataDir, '_tokens.json');
|
|
200
|
-
if (!fs_1.default.existsSync(tokenPath)) {
|
|
201
|
-
logger_js_1.logger.error(`_tokens.json이 없습니다: ${tokenPath}`);
|
|
202
|
-
return { totalCount: 0, categories: [], groups: [] };
|
|
203
|
-
}
|
|
204
|
-
try {
|
|
205
|
-
const raw = fs_1.default.readFileSync(tokenPath, 'utf-8');
|
|
206
|
-
const parsed = JSON.parse(raw);
|
|
207
|
-
if (!isValidTokenJson(parsed)) {
|
|
208
|
-
throw new Error('totalCount 또는 groups 배열이 없습니다');
|
|
209
|
-
}
|
|
210
|
-
return parsed;
|
|
211
|
-
}
|
|
212
|
-
catch (err) {
|
|
213
|
-
logger_js_1.logger.error(`_tokens.json 파싱 실패: ${(0, response_js_1.toErrorMessage)(err)}`);
|
|
214
|
-
return { totalCount: 0, categories: [], groups: [] };
|
|
215
|
-
}
|
|
216
|
-
};
|
|
217
|
-
exports.loadTokenData = loadTokenData;
|
|
218
|
-
/** TokenData 스키마 검증 */
|
|
219
|
-
const isValidTokenJson = (data) => !!data &&
|
|
220
|
-
typeof data === 'object' &&
|
|
221
|
-
'totalCount' in data &&
|
|
222
|
-
'groups' in data &&
|
|
223
|
-
Array.isArray(data.groups);
|
|
224
|
-
/** compliance-rules.json 스키마 검증 */
|
|
225
|
-
const isValidComplianceRulesJson = (data) => !!data && typeof data === 'object' && 'patterns' in data && Array.isArray(data.patterns);
|
|
226
|
-
/** definitions/compliance-rules.json을 로드하여 ComplianceRulesData로 반환 (파일 없으면 null) */
|
|
227
|
-
const loadComplianceRules = (definitionsDir) => {
|
|
228
|
-
const filePath = path_1.default.resolve(definitionsDir, 'compliance-rules.json');
|
|
229
|
-
if (!fs_1.default.existsSync(filePath)) {
|
|
230
|
-
logger_js_1.logger.info('compliance-rules.json이 없습니다 — compliance 검증 비활성화');
|
|
231
|
-
return null;
|
|
232
|
-
}
|
|
233
|
-
try {
|
|
234
|
-
const raw = fs_1.default.readFileSync(filePath, 'utf-8');
|
|
235
|
-
const parsed = JSON.parse(raw);
|
|
236
|
-
if (!isValidComplianceRulesJson(parsed)) {
|
|
237
|
-
throw new Error('patterns 배열이 없습니다');
|
|
238
|
-
}
|
|
239
|
-
return parsed;
|
|
240
|
-
}
|
|
241
|
-
catch (err) {
|
|
242
|
-
logger_js_1.logger.error(`compliance-rules.json 파싱 실패: ${(0, response_js_1.toErrorMessage)(err)}`);
|
|
243
|
-
return null;
|
|
244
|
-
}
|
|
245
|
-
};
|
|
246
|
-
exports.loadComplianceRules = loadComplianceRules;
|
|
247
|
-
/** definitions/instructions.md를 로드하여 문자열 반환 */
|
|
248
|
-
const loadInstructions = (definitionsDir) => {
|
|
249
|
-
const instructionsPath = path_1.default.resolve(definitionsDir, 'instructions.md');
|
|
250
|
-
return fs_1.default.readFileSync(instructionsPath, 'utf-8').trim();
|
|
251
|
-
};
|
|
252
|
-
exports.loadInstructions = loadInstructions;
|
|
253
|
-
/** definitions/js-api.json을 로드하여 exportName→JsApiInfo Map 반환 */
|
|
254
|
-
const loadJsApi = (definitionsDir) => {
|
|
255
|
-
const filePath = path_1.default.resolve(definitionsDir, 'js-api.json');
|
|
256
|
-
if (!fs_1.default.existsSync(filePath)) {
|
|
257
|
-
logger_js_1.logger.info('js-api.json이 없습니다 — JS API 힌트 비활성화');
|
|
258
|
-
return new Map();
|
|
259
|
-
}
|
|
260
|
-
try {
|
|
261
|
-
const raw = fs_1.default.readFileSync(filePath, 'utf-8');
|
|
262
|
-
const data = JSON.parse(raw);
|
|
263
|
-
return new Map(Object.entries(data));
|
|
264
|
-
}
|
|
265
|
-
catch (err) {
|
|
266
|
-
logger_js_1.logger.error(`js-api.json 파싱 실패: ${(0, response_js_1.toErrorMessage)(err)}`);
|
|
267
|
-
return new Map();
|
|
268
|
-
}
|
|
269
|
-
};
|
|
270
|
-
exports.loadJsApi = loadJsApi;
|
|
271
|
-
// ── 순수 함수 (외부 상태 없음, Map을 파라미터로 받음) ──────────────────────
|
|
272
|
-
/** componentMap에서 이름으로 단일 컴포넌트 조회 */
|
|
273
|
-
const getComponent = (map, name) => map.get(name);
|
|
274
|
-
exports.getComponent = getComponent;
|
|
275
|
-
/** componentMap의 모든 컴포넌트를 배열로 반환 */
|
|
276
|
-
const getAllComponents = (map) => Array.from(map.values());
|
|
277
|
-
exports.getAllComponents = getAllComponents;
|
|
1
|
+
"use strict";var e=this&&this.__importDefault||function(e){return e&&e.__esModule?e:{default:e}};Object.defineProperty(exports,"__esModule",{value:!0}),exports.loadTokenData=exports.loadMeta=exports.loadJsApi=exports.loadInstructions=exports.loadIconData=exports.loadComponentsFromDir=exports.loadCompositionOverrides=exports.loadComplianceRules=exports.getComponent=exports.getAllComponents=exports.DEFAULT_DATA_DIR=void 0;const o=e(require("fs")),r=e(require("path")),t=require("./logger.js"),n=require("./response.js"),s=r.default.resolve(__dirname,"../../data");exports.DEFAULT_DATA_DIR=s;const a=[r.default.resolve(__dirname,"../../src/overrides/composition.json"),r.default.resolve(__dirname,"../overrides/composition.json")],i=e=>{if("object"!=typeof e||!e)return{};const o={};for(const[r,t]of Object.entries(e))r.startsWith("_")||t&&"object"==typeof t&&!Array.isArray(t)&&(o[r]=t);return o},l=()=>{for(const e of a)try{const n=o.default.readFileSync(e,"utf-8"),s=JSON.parse(n),a=i(s);return t.logger.info(`composition overrides 로딩: ${Object.keys(a).length}개 (${r.default.basename(e)})`),a}catch(o){if(o instanceof Error&&"code"in o&&"ENOENT"===o.code)continue;return t.logger.warn(`composition overrides 파싱 실패 (${e}): ${(0,n.toErrorMessage)(o)}`),{}}return{}};exports.loadCompositionOverrides=l;const c=e=>{if(!e)return e;const o={};for(const[r,t]of Object.entries(e))r.startsWith("_")||(o[r]=t);return o},u=(e,o)=>{if(o.bemClassesExtra&&o.bemClassesExtra.length>0){const r=new Set([...e.bemClasses,...o.bemClassesExtra]);e.bemClasses=[...r].sort()}if(o.allowedChildren&&(e.allowedChildren=o.allowedChildren),o.allowedParents&&(e.allowedParents=o.allowedParents),o.canonicalExample&&(e.canonicalExample=c(o.canonicalExample)),o.canonicalExamples&&(e.canonicalExamples=c(o.canonicalExamples)),o.descriptionExtra&&(e.description=`${e.description}\n\n${o.descriptionExtra}`),o.aliasesExtra&&o.aliasesExtra.length>0){const r=new Set([...e.aliases??[],...o.aliasesExtra]);e.aliases=[...r]}},p=e=>!!e&&"object"==typeof e&&"name"in e&&"string"==typeof e.name,d=e=>!!e&&"object"==typeof e&&"icons"in e&&Array.isArray(e.icons),f=e=>{if(!o.default.existsSync(e))throw new Error(`mcp/data/ 디렉토리가 없습니다: ${e}`);const n=o.default.readdirSync(e).filter(e=>e.endsWith(".json")&&!e.startsWith("_"));if(0===n.length)throw new Error(`mcp/data/ 디렉토리에 JSON 파일이 없습니다: ${e}`);const s=new Map,a=l();for(const t of n){const n=r.default.join(e,t),i=o.default.readFileSync(n,"utf-8"),l=JSON.parse(i);if(!p(l))throw new Error(`JSON 파싱 실패 (${t}): name 필드가 없거나 올바르지 않습니다`);const c=l,d=a[c.name];d&&u(c,d),s.set(c.name,c)}return t.logger.info(`컴포넌트 ${s.size}개 로딩 완료`),{map:s,compositionOverrides:a}};exports.loadComponentsFromDir=f;const g=e=>{const s=r.default.join(e,"_icons.json");if(!o.default.existsSync(s))return t.logger.error(`_icons.json이 없습니다: ${s}`),{totalCount:0,fillCount:0,icons:[]};try{const e=o.default.readFileSync(s,"utf-8"),r=JSON.parse(e);if(!d(r))throw new Error("icons 배열이 없습니다");return r}catch(e){return t.logger.error(`_icons.json 파싱 실패: ${(0,n.toErrorMessage)(e)}`),{totalCount:0,fillCount:0,icons:[]}}};exports.loadIconData=g;const m=e=>{const s=r.default.join(e,"_meta.json");if(!o.default.existsSync(s))return{cdn:null,icon:null};try{const e=o.default.readFileSync(s,"utf-8"),r=JSON.parse(e);return{cdn:x(r.cdn)?r.cdn:null,icon:y(r.icon)?r.icon:null}}catch(e){return t.logger.error(`_meta.json 파싱 실패: ${(0,n.toErrorMessage)(e)}`),{cdn:null,icon:null}}};exports.loadMeta=m;const x=e=>!!e&&"object"==typeof e&&"version"in e&&"css"in e&&"js"in e&&"string"==typeof e.version,y=e=>!!e&&"object"==typeof e&&"packageName"in e&&"version"in e&&"string"==typeof e.packageName,j=e=>{const s=r.default.join(e,"_tokens.json");if(!o.default.existsSync(s))return t.logger.error(`_tokens.json이 없습니다: ${s}`),{totalCount:0,categories:[],groups:[]};try{const e=o.default.readFileSync(s,"utf-8"),r=JSON.parse(e);if(!E(r))throw new Error("totalCount 또는 groups 배열이 없습니다");return r}catch(e){return t.logger.error(`_tokens.json 파싱 실패: ${(0,n.toErrorMessage)(e)}`),{totalCount:0,categories:[],groups:[]}}};exports.loadTokenData=j;const E=e=>!!e&&"object"==typeof e&&"totalCount"in e&&"groups"in e&&Array.isArray(e.groups),S=e=>!!e&&"object"==typeof e&&"patterns"in e&&Array.isArray(e.patterns),_=e=>{const s=r.default.resolve(e,"compliance-rules.json");if(!o.default.existsSync(s))return t.logger.info("compliance-rules.json이 없습니다 — compliance 검증 비활성화"),null;try{const e=o.default.readFileSync(s,"utf-8"),r=JSON.parse(e);if(!S(r))throw new Error("patterns 배열이 없습니다");return r}catch(e){return t.logger.error(`compliance-rules.json 파싱 실패: ${(0,n.toErrorMessage)(e)}`),null}};exports.loadComplianceRules=_;const h=e=>{const t=r.default.resolve(e,"instructions.md");return o.default.readFileSync(t,"utf-8").trim()};exports.loadInstructions=h;const C=e=>{const s=r.default.resolve(e,"js-api.json");if(!o.default.existsSync(s))return t.logger.info("js-api.json이 없습니다 — JS API 힌트 비활성화"),new Map;try{const e=o.default.readFileSync(s,"utf-8"),r=JSON.parse(e);return new Map(Object.entries(r))}catch(e){return t.logger.error(`js-api.json 파싱 실패: ${(0,n.toErrorMessage)(e)}`),new Map}};exports.loadJsApi=C;const w=(e,o)=>e.get(o);exports.getComponent=w;const v=e=>Array.from(e.values());exports.getAllComponents=v;
|
|
@@ -1,77 +1 @@
|
|
|
1
|
-
"use strict";
|
|
2
|
-
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
|
-
exports.setupDomEnvironment = void 0;
|
|
4
|
-
/**
|
|
5
|
-
* monorepo에서 패키지가 여러 node_modules에 설치될 수 있음.
|
|
6
|
-
* 번들의 external require가 MCP 로컬 node_modules의 버전을 사용하도록 통일한다.
|
|
7
|
-
*
|
|
8
|
-
* 문제: esbuild 번들은 ui-admin cwd에서 빌드되므로, external require가
|
|
9
|
-
* 루트 node_modules(오래된 버전)를 resolve할 수 있다.
|
|
10
|
-
* 해결: MCP 패키지 기준 require.resolve로 경로를 고정한다.
|
|
11
|
-
*/
|
|
12
|
-
const unifyModuleResolution = () => {
|
|
13
|
-
// eslint-disable-next-line @typescript-eslint/no-require-imports, @typescript-eslint/no-var-requires
|
|
14
|
-
const Module = require('module');
|
|
15
|
-
const overrides = {};
|
|
16
|
-
const pin = (name) => {
|
|
17
|
-
try {
|
|
18
|
-
overrides[name] = require.resolve(name);
|
|
19
|
-
}
|
|
20
|
-
catch {
|
|
21
|
-
/* 미설치 패키지는 무시 */
|
|
22
|
-
}
|
|
23
|
-
};
|
|
24
|
-
// React 인스턴스 통일 (Hook dispatcher 일치 보장)
|
|
25
|
-
pin('react');
|
|
26
|
-
pin('react-dom');
|
|
27
|
-
pin('react/jsx-runtime');
|
|
28
|
-
// external 패키지 버전 통일 (monorepo hoisting 불일치 방지)
|
|
29
|
-
pin('@ncds/ui-admin-icon');
|
|
30
|
-
pin('swiper/react');
|
|
31
|
-
pin('swiper');
|
|
32
|
-
const origResolve = Module._resolveFilename;
|
|
33
|
-
Module._resolveFilename = function (request, parent, isMain, options) {
|
|
34
|
-
if (request in overrides)
|
|
35
|
-
return overrides[request];
|
|
36
|
-
return origResolve.call(this, request, parent, isMain, options);
|
|
37
|
-
};
|
|
38
|
-
};
|
|
39
|
-
/** jsdom + React SSR 환경을 초기화하고 ReactRuntime을 반환 */
|
|
40
|
-
const setupDomEnvironment = () => {
|
|
41
|
-
unifyModuleResolution();
|
|
42
|
-
// eslint-disable-next-line @typescript-eslint/no-require-imports, @typescript-eslint/no-var-requires
|
|
43
|
-
const { JSDOM } = require('jsdom');
|
|
44
|
-
const dom = new JSDOM('<!DOCTYPE html><html><body></body></html>');
|
|
45
|
-
if (typeof globalThis.document === 'undefined')
|
|
46
|
-
globalThis.document = dom.window.document;
|
|
47
|
-
if (typeof globalThis.window === 'undefined')
|
|
48
|
-
globalThis.window = dom.window;
|
|
49
|
-
/* v8 ignore next 3 -- Node.js 환경에서 navigator 미존재 시 설정. 테스트 실행 순서에 따라 이미 설정된 경우 스킵 */
|
|
50
|
-
if (typeof globalThis.navigator === 'undefined') {
|
|
51
|
-
Object.defineProperty(globalThis, 'navigator', { value: dom.window.navigator, writable: true });
|
|
52
|
-
}
|
|
53
|
-
// Swiper 등 외부 라이브러리가 HTMLElement를 직접 참조 — jsdom에서 전역 노출
|
|
54
|
-
for (const key of [
|
|
55
|
-
'HTMLElement',
|
|
56
|
-
'HTMLDivElement',
|
|
57
|
-
'HTMLSpanElement',
|
|
58
|
-
'Element',
|
|
59
|
-
'Node',
|
|
60
|
-
'Event',
|
|
61
|
-
'CustomEvent',
|
|
62
|
-
'Image',
|
|
63
|
-
]) {
|
|
64
|
-
if (typeof globalThis[key] === 'undefined') {
|
|
65
|
-
globalThis[key] = dom.window[key];
|
|
66
|
-
}
|
|
67
|
-
}
|
|
68
|
-
// eslint-disable-next-line @typescript-eslint/no-require-imports, @typescript-eslint/no-var-requires
|
|
69
|
-
const ReactDOM = require('react-dom');
|
|
70
|
-
ReactDOM.createPortal = (children) => children;
|
|
71
|
-
// eslint-disable-next-line @typescript-eslint/no-require-imports, @typescript-eslint/no-var-requires
|
|
72
|
-
const React = require('react');
|
|
73
|
-
// eslint-disable-next-line @typescript-eslint/no-require-imports, @typescript-eslint/no-var-requires
|
|
74
|
-
const { renderToStaticMarkup } = require('react-dom/server');
|
|
75
|
-
return { createElement: React.createElement, renderToStaticMarkup };
|
|
76
|
-
};
|
|
77
|
-
exports.setupDomEnvironment = setupDomEnvironment;
|
|
1
|
+
"use strict";Object.defineProperty(exports,"__esModule",{value:!0}),exports.setupDomEnvironment=void 0;const e=()=>{const e=require("module"),o={},t=e=>{try{o[e]=require.resolve(e)}catch{}};t("react"),t("react-dom"),t("react/jsx-runtime"),t("@ncds/ui-admin-icon"),t("swiper/react"),t("swiper");const r=e._resolveFilename;e._resolveFilename=function(e,t,n,i){return e in o?o[e]:r.call(this,e,t,n,i)}},o=()=>{e();const{JSDOM:o}=require("jsdom"),t=new o("<!DOCTYPE html><html><body></body></html>");void 0===globalThis.document&&(globalThis.document=t.window.document),void 0===globalThis.window&&(globalThis.window=t.window),void 0===globalThis.navigator&&Object.defineProperty(globalThis,"navigator",{value:t.window.navigator,writable:!0});for(const e of["HTMLElement","HTMLDivElement","HTMLSpanElement","Element","Node","Event","CustomEvent","Image"])void 0===globalThis[e]&&(globalThis[e]=t.window[e]);require("react-dom").createPortal=e=>e;const r=require("react"),{renderToStaticMarkup:n}=require("react-dom/server");return{createElement:r.createElement,renderToStaticMarkup:n}};exports.setupDomEnvironment=o;
|
package/bin/utils/fuzzyMatch.js
CHANGED
|
@@ -1,120 +1 @@
|
|
|
1
|
-
"use strict";
|
|
2
|
-
/**
|
|
3
|
-
* 한국어/영어 퍼지 매칭 유틸
|
|
4
|
-
*
|
|
5
|
-
* - 공백 제거 후 비교 ("약관동의" ↔ "약관 동의")
|
|
6
|
-
* - 부분 포함 ("비번" ⊂ "비밀번호"는 안 되지만, "로딩" ⊂ "로딩중"은 됨)
|
|
7
|
-
* - 편집 거리(Levenshtein) 기반 오타 허용 ("셀랙트" ↔ "셀렉트", "buttn" ↔ "button")
|
|
8
|
-
*/
|
|
9
|
-
Object.defineProperty(exports, "__esModule", { value: true });
|
|
10
|
-
exports.bestFuzzyMatch = exports.fuzzyMatch = exports.similarity = exports.editDistance = void 0;
|
|
11
|
-
// ── 상수 ──────────────────────────────────────────────────────────
|
|
12
|
-
/** 포함 매칭 시 기본 점수 */
|
|
13
|
-
const CONTAINS_BASE_SCORE = 0.7;
|
|
14
|
-
/** 포함 매칭 시 길이 비율 보너스 가중치 */
|
|
15
|
-
const CONTAINS_BONUS_MULTIPLIER = 0.2;
|
|
16
|
-
/** 이 길이 이하의 쿼리는 "짧은 쿼리"로 분류 (typo threshold = 1) */
|
|
17
|
-
const SHORT_QUERY_LENGTH = 2;
|
|
18
|
-
/** 긴 쿼리의 오타 허용 비율 (ceil(length * 이 값)) */
|
|
19
|
-
const TYPO_THRESHOLD_RATIO = 0.3;
|
|
20
|
-
/** target이 query보다 이만큼 이상 길면 부분 문자열 매칭 시도 */
|
|
21
|
-
const LENGTH_DIFF_FOR_SUBSTRING = 2;
|
|
22
|
-
/** 부분 문자열 매칭을 시도할 target 최대 길이 */
|
|
23
|
-
const SUBSTRING_TARGET_LIMIT = 100;
|
|
24
|
-
/** 부분 문자열 퍼지 매칭 시 기본 점수 */
|
|
25
|
-
const SUBSTRING_BASE_SCORE = 0.5;
|
|
26
|
-
/** 부분 문자열 퍼지 매칭 시 유사도 보너스 가중치 */
|
|
27
|
-
const SUBSTRING_BONUS_MULTIPLIER = 0.2;
|
|
28
|
-
// ── 순수 함수 ────────────────────────────────────────────────────
|
|
29
|
-
/** 공백·하이픈 제거 후 소문자 */
|
|
30
|
-
const normalize = (s) => s.toLowerCase().replace(/[\s\-_]/g, '');
|
|
31
|
-
/** Levenshtein 편집 거리 */
|
|
32
|
-
const editDistance = (a, b) => {
|
|
33
|
-
const m = a.length;
|
|
34
|
-
const n = b.length;
|
|
35
|
-
if (m === 0)
|
|
36
|
-
return n;
|
|
37
|
-
if (n === 0)
|
|
38
|
-
return m;
|
|
39
|
-
// 짧은 배열만 유지 (메모리 최적화)
|
|
40
|
-
let prev = Array.from({ length: n + 1 }, (_, i) => i);
|
|
41
|
-
let curr = new Array(n + 1);
|
|
42
|
-
for (let i = 1; i <= m; i++) {
|
|
43
|
-
curr[0] = i;
|
|
44
|
-
for (let j = 1; j <= n; j++) {
|
|
45
|
-
const cost = a[i - 1] === b[j - 1] ? 0 : 1;
|
|
46
|
-
curr[j] = Math.min(prev[j] + 1, curr[j - 1] + 1, prev[j - 1] + cost);
|
|
47
|
-
}
|
|
48
|
-
[prev, curr] = [curr, prev];
|
|
49
|
-
}
|
|
50
|
-
return prev[n];
|
|
51
|
-
};
|
|
52
|
-
exports.editDistance = editDistance;
|
|
53
|
-
/** 편집 거리 기반 유사도 (0~1, 1이 완전 일치) */
|
|
54
|
-
const similarity = (a, b) => {
|
|
55
|
-
const maxLen = Math.max(a.length, b.length);
|
|
56
|
-
if (maxLen === 0)
|
|
57
|
-
return 1;
|
|
58
|
-
return 1 - (0, exports.editDistance)(a, b) / maxLen;
|
|
59
|
-
};
|
|
60
|
-
exports.similarity = similarity;
|
|
61
|
-
/**
|
|
62
|
-
* query가 target에 매칭되는지 퍼지 검사
|
|
63
|
-
*
|
|
64
|
-
* @returns null이면 매칭 실패, FuzzyResult면 매칭 성공
|
|
65
|
-
*/
|
|
66
|
-
// biome-ignore lint/complexity/noExcessiveCognitiveComplexity: 정확/포함/퍼지 단계별 매칭 분기 — 알고리즘 본질, 별 리팩토링 거리
|
|
67
|
-
const fuzzyMatch = (query, target) => {
|
|
68
|
-
const q = normalize(query);
|
|
69
|
-
const t = normalize(target);
|
|
70
|
-
if (!q || !t)
|
|
71
|
-
return null;
|
|
72
|
-
// 1. 정확 일치 (공백 제거 후)
|
|
73
|
-
if (t === q)
|
|
74
|
-
return { score: 1.0, matchType: 'exact' };
|
|
75
|
-
// 2. 포함 매칭 (공백 제거 후)
|
|
76
|
-
if (t.includes(q) || q.includes(t)) {
|
|
77
|
-
const shorter = Math.min(q.length, t.length);
|
|
78
|
-
const longer = Math.max(q.length, t.length);
|
|
79
|
-
return { score: CONTAINS_BASE_SCORE + CONTAINS_BONUS_MULTIPLIER * (shorter / longer), matchType: 'contains' };
|
|
80
|
-
}
|
|
81
|
-
// 3. 편집 거리 기반 퍼지 매칭
|
|
82
|
-
const isShortQuery = q.length <= SHORT_QUERY_LENGTH;
|
|
83
|
-
const typoThreshold = isShortQuery ? 1 : Math.ceil(q.length * TYPO_THRESHOLD_RATIO);
|
|
84
|
-
const dist = (0, exports.editDistance)(q, t);
|
|
85
|
-
// target이 query보다 훨씬 길면, 부분 문자열 슬라이딩 윈도우로 비교
|
|
86
|
-
const targetIsLonger = t.length > q.length + LENGTH_DIFF_FOR_SUBSTRING;
|
|
87
|
-
const queryIsLongEnough = q.length >= SHORT_QUERY_LENGTH;
|
|
88
|
-
const targetWithinLimit = t.length <= SUBSTRING_TARGET_LIMIT;
|
|
89
|
-
const shouldTrySubstringMatch = targetIsLonger && queryIsLongEnough && targetWithinLimit;
|
|
90
|
-
if (shouldTrySubstringMatch) {
|
|
91
|
-
for (let i = 0; i <= t.length - q.length; i++) {
|
|
92
|
-
const sub = t.substring(i, i + q.length);
|
|
93
|
-
const subDist = (0, exports.editDistance)(q, sub);
|
|
94
|
-
if (subDist <= typoThreshold) {
|
|
95
|
-
const subSimilarity = q.length === 0 ? 1 : 1 - subDist / q.length;
|
|
96
|
-
return { score: SUBSTRING_BASE_SCORE + SUBSTRING_BONUS_MULTIPLIER * subSimilarity, matchType: 'fuzzy' };
|
|
97
|
-
}
|
|
98
|
-
}
|
|
99
|
-
}
|
|
100
|
-
if (dist <= typoThreshold) {
|
|
101
|
-
const maxLen = Math.max(q.length, t.length);
|
|
102
|
-
return { score: maxLen === 0 ? 1 : 1 - dist / maxLen, matchType: 'fuzzy' };
|
|
103
|
-
}
|
|
104
|
-
return null;
|
|
105
|
-
};
|
|
106
|
-
exports.fuzzyMatch = fuzzyMatch;
|
|
107
|
-
/**
|
|
108
|
-
* query를 여러 targets에 대해 퍼지 매칭하고 최고 점수를 반환
|
|
109
|
-
*/
|
|
110
|
-
const bestFuzzyMatch = (query, targets) => {
|
|
111
|
-
let best = null;
|
|
112
|
-
for (const target of targets) {
|
|
113
|
-
const result = (0, exports.fuzzyMatch)(query, target);
|
|
114
|
-
if (result && (!best || result.score > best.score)) {
|
|
115
|
-
best = result;
|
|
116
|
-
}
|
|
117
|
-
}
|
|
118
|
-
return best;
|
|
119
|
-
};
|
|
120
|
-
exports.bestFuzzyMatch = bestFuzzyMatch;
|
|
1
|
+
"use strict";Object.defineProperty(exports,"__esModule",{value:!0}),exports.bestFuzzyMatch=exports.fuzzyMatch=exports.similarity=exports.editDistance=void 0;const t=.7,e=.2,n=2,r=.3,s=2,o=100,c=.5,l=.2,h=t=>t.toLowerCase().replace(/[\s\-_]/g,""),i=(t,e)=>{const n=t.length,r=e.length;if(0===n)return r;if(0===r)return n;let s=Array.from({length:r+1},(t,e)=>e),o=new Array(r+1);for(let c=1;c<=n;c++){o[0]=c;for(let n=1;n<=r;n++){const r=t[c-1]===e[n-1]?0:1;o[n]=Math.min(s[n]+1,o[n-1]+1,s[n-1]+r)}[s,o]=[o,s]}return s[r]};exports.editDistance=i;const a=(t,e)=>{const n=Math.max(t.length,e.length);return 0===n?1:1-(0,exports.editDistance)(t,e)/n};exports.similarity=a;const u=(n,s)=>{const i=h(n),a=h(s);if(!i||!a)return null;if(a===i)return{score:1,matchType:"exact"};if(a.includes(i)||i.includes(a)){const n=Math.min(i.length,a.length),r=Math.max(i.length,a.length);return{score:t+e*(n/r),matchType:"contains"}}const u=i.length<=2?1:Math.ceil(i.length*r),g=(0,exports.editDistance)(i,a),f=a.length>i.length+2,p=i.length>=2,x=a.length<=o;if(f&&p&&x)for(let t=0;t<=a.length-i.length;t++){const e=a.substring(t,t+i.length),n=(0,exports.editDistance)(i,e);if(n<=u){const t=0===i.length?1:1-n/i.length;return{score:c+l*t,matchType:"fuzzy"}}}if(g<=u){const t=Math.max(i.length,a.length);return{score:0===t?1:1-g/t,matchType:"fuzzy"}}return null};exports.fuzzyMatch=u;const g=(t,e)=>{let n=null;for(const r of e){const e=(0,exports.fuzzyMatch)(t,r);e&&(!n||e.score>n.score)&&(n=e)}return n};exports.bestFuzzyMatch=g;
|
package/bin/utils/logger.js
CHANGED
|
@@ -1,27 +1 @@
|
|
|
1
|
-
"use strict";
|
|
2
|
-
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
|
-
exports.logger = void 0;
|
|
4
|
-
/**
|
|
5
|
-
* stderr 전용 logger 유틸리티 — 부수효과 함수 (stderr I/O)
|
|
6
|
-
*
|
|
7
|
-
* console.log / console.warn / console.error 직접 사용 금지.
|
|
8
|
-
*
|
|
9
|
-
* MCP stdio 프로토콜은 채널 역할이 엄격히 분리된다.
|
|
10
|
-
* stdin → 클라이언트 → 서버 요청(JSON-RPC)
|
|
11
|
-
* stdout → 서버 → 클라이언트 응답(JSON-RPC). 프로토콜 전용 채널
|
|
12
|
-
* stderr → 로그 전용 (프로토콜 밖)
|
|
13
|
-
*
|
|
14
|
-
* console.log는 stdout을 오염시켜 JSON-RPC 스트림을 깨뜨린다.
|
|
15
|
-
* 모든 로깅은 이 logger를 통해 stderr로 출력한다.
|
|
16
|
-
*/
|
|
17
|
-
exports.logger = {
|
|
18
|
-
info: (msg) => {
|
|
19
|
-
process.stderr.write(`[INFO] ${msg}\n`);
|
|
20
|
-
},
|
|
21
|
-
warn: (msg) => {
|
|
22
|
-
process.stderr.write(`[WARN] ${msg}\n`);
|
|
23
|
-
},
|
|
24
|
-
error: (msg) => {
|
|
25
|
-
process.stderr.write(`[ERROR] ${msg}\n`);
|
|
26
|
-
},
|
|
27
|
-
};
|
|
1
|
+
"use strict";Object.defineProperty(exports,"__esModule",{value:!0}),exports.logger=void 0,exports.logger={info:r=>{process.stderr.write(`[INFO] ${r}\n`)},warn:r=>{process.stderr.write(`[WARN] ${r}\n`)},error:r=>{process.stderr.write(`[ERROR] ${r}\n`)}};
|
package/bin/utils/response.js
CHANGED
|
@@ -1,39 +1 @@
|
|
|
1
|
-
"use strict";
|
|
2
|
-
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
|
-
exports.toErrorMessage = exports.normalizeName = exports.appendPingReminder = exports.componentNotFoundResponse = exports.errorResponse = exports.successResponse = void 0;
|
|
4
|
-
/** 성공 응답 — data를 JSON.stringify하여 반환 */
|
|
5
|
-
const successResponse = (data) => ({
|
|
6
|
-
content: [{ type: 'text', text: JSON.stringify(data) }],
|
|
7
|
-
});
|
|
8
|
-
exports.successResponse = successResponse;
|
|
9
|
-
/** 오류 응답 — code + message + suggestion + optional warnings 구조로 isError: true 반환 */
|
|
10
|
-
const errorResponse = (code, message, suggestion, warnings) => ({
|
|
11
|
-
content: [
|
|
12
|
-
{
|
|
13
|
-
type: 'text',
|
|
14
|
-
text: JSON.stringify({
|
|
15
|
-
code,
|
|
16
|
-
message,
|
|
17
|
-
suggestion,
|
|
18
|
-
...(warnings && warnings.length > 0 && { warnings }),
|
|
19
|
-
}),
|
|
20
|
-
},
|
|
21
|
-
],
|
|
22
|
-
isError: true,
|
|
23
|
-
});
|
|
24
|
-
exports.errorResponse = errorResponse;
|
|
25
|
-
/** COMPONENT_NOT_FOUND 오류 — 여러 tool에서 공통 사용 */
|
|
26
|
-
const componentNotFoundResponse = (name) => (0, exports.errorResponse)('COMPONENT_NOT_FOUND', `Component '${name}' not found.`, 'Use search_component to find similar components, or list_components to see all available.');
|
|
27
|
-
exports.componentNotFoundResponse = componentNotFoundResponse;
|
|
28
|
-
/** ping 미호출 시 응답에 핵심 rules를 주입하는 래퍼 */
|
|
29
|
-
const appendPingReminder = (response, reminder) => {
|
|
30
|
-
const text = `${response.content[0].text}\n\n${reminder}`;
|
|
31
|
-
return { ...response, content: [{ type: 'text', text }] };
|
|
32
|
-
};
|
|
33
|
-
exports.appendPingReminder = appendPingReminder;
|
|
34
|
-
/** 입력 정규화 — name.trim().toLowerCase() */
|
|
35
|
-
const normalizeName = (name) => name.trim().toLowerCase();
|
|
36
|
-
exports.normalizeName = normalizeName;
|
|
37
|
-
/** catch 블록에서 에러 메시지를 안전하게 추출 */
|
|
38
|
-
const toErrorMessage = (err) => (err instanceof Error ? err.message : String(err));
|
|
39
|
-
exports.toErrorMessage = toErrorMessage;
|
|
1
|
+
"use strict";Object.defineProperty(exports,"__esModule",{value:!0}),exports.toErrorMessage=exports.normalizeName=exports.appendPingReminder=exports.componentNotFoundResponse=exports.errorResponse=exports.successResponse=void 0;const e=e=>({content:[{type:"text",text:JSON.stringify(e)}]});exports.successResponse=e;const t=(e,t,o,s)=>({content:[{type:"text",text:JSON.stringify({code:e,message:t,suggestion:o,...s&&s.length>0&&{warnings:s}})}],isError:!0});exports.errorResponse=t;const o=e=>(0,exports.errorResponse)("COMPONENT_NOT_FOUND",`Component '${e}' not found.`,"Use search_component to find similar components, or list_components to see all available.");exports.componentNotFoundResponse=o;const s=(e,t)=>{const o=`${e.content[0].text}\n\n${t}`;return{...e,content:[{type:"text",text:o}]}};exports.appendPingReminder=s;const n=e=>e.trim().toLowerCase();exports.normalizeName=n;const r=e=>e instanceof Error?e.message:String(e);exports.toErrorMessage=r;
|