@ncds/ui-admin-mcp 1.0.0-alpha.2 → 1.0.0-alpha.20
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 +15 -19
- package/bin/definitions/external/editor.d.ts +50 -0
- package/bin/definitions/external/editor.js +53 -0
- package/bin/definitions/external/step-guide.d.ts +61 -0
- package/bin/definitions/external/step-guide.js +52 -0
- package/bin/definitions/instructions.md +194 -10
- package/bin/definitions/js-api.json +352 -0
- package/bin/definitions/rules.json +36 -4
- package/bin/definitions/tool-definitions.json +33 -9
- package/bin/overrides/composition.json +2500 -0
- package/bin/server.d.ts +5 -0
- package/bin/server.js +97 -33
- package/bin/server.mjs +0 -0
- package/bin/tools/external/editor.d.ts +18 -0
- package/bin/tools/external/editor.js +88 -0
- package/bin/tools/external/step-guide.d.ts +19 -0
- package/bin/tools/external/step-guide.js +79 -0
- package/bin/tools/getComponentProps.d.ts +3 -0
- package/bin/tools/getComponentProps.js +12 -3
- package/bin/tools/listCompositionOverrides.d.ts +61 -0
- package/bin/tools/listCompositionOverrides.js +156 -0
- package/bin/tools/ping.d.ts +1 -1
- package/bin/tools/renderToHtml.d.ts +38 -7
- package/bin/tools/renderToHtml.js +785 -110
- package/bin/tools/searchComponent.d.ts +5 -0
- package/bin/tools/searchComponent.js +3 -3
- package/bin/tools/validateHtml.d.ts +8 -6
- package/bin/tools/validateHtml.js +74 -6
- package/bin/types.d.ts +60 -1
- package/bin/utils/bemValidator.d.ts +16 -8
- package/bin/utils/bemValidator.js +16 -4
- package/bin/utils/compliance.d.ts +7 -6
- package/bin/utils/compliance.js +8 -4
- package/bin/utils/dataLoader.d.ts +43 -14
- package/bin/utils/dataLoader.js +125 -22
- package/bin/utils/domEnvironment.js +51 -0
- package/bin/utils/fuzzyMatch.d.ts +4 -0
- package/bin/utils/fuzzyMatch.js +13 -3
- package/bin/utils/logger.d.ts +5 -5
- package/bin/utils/logger.js +5 -5
- package/bin/utils/response.d.ts +4 -2
- package/bin/utils/response.js +15 -4
- package/bin/utils/tokenValidator.d.ts +4 -3
- package/bin/utils/tokenValidator.js +13 -11
- package/bin/version.d.ts +4 -2
- package/bin/version.js +4 -2
- package/data/_icons.json +357 -2
- package/data/_meta.json +4 -5
- package/data/_tokens.json +8 -8
- package/data/badge-group.json +181 -4
- package/data/badge.json +146 -14
- package/data/block-container.json +95 -0
- package/data/block-header.json +208 -0
- package/data/bread-crumb.json +38 -2
- package/data/button-group.json +59 -0
- package/data/button.json +124 -1
- package/data/carousel-arrow.json +6 -11
- package/data/carousel-number-group.json +2 -12
- package/data/checkbox.json +1 -1
- package/data/combo-box.json +32 -8
- package/data/data-grid.json +240 -0
- package/data/date-picker.json +22 -2
- package/data/divider.json +1 -1
- package/data/dot.json +2 -2
- package/data/dropdown.json +187 -20
- package/data/editor.json +85 -0
- package/data/empty-state.json +168 -3
- package/data/featured-icon.json +20 -5
- package/data/file-input.json +176 -10
- package/data/horizontal-tab.json +219 -3
- package/data/image-file-input.json +176 -10
- package/data/input-base.json +165 -4
- package/data/modal.json +266 -5
- package/data/notification.json +56 -40
- package/data/number-input.json +164 -4
- package/data/page-title.json +135 -0
- package/data/pagination.json +8 -4
- package/data/password-input.json +252 -13
- package/data/progress-bar.json +28 -8
- package/data/progress-circle.json +9 -6
- package/data/radio.json +4 -3
- package/data/range-date-picker-with-buttons.json +187 -7
- package/data/range-date-picker.json +186 -6
- package/data/select-box.json +48 -16
- package/data/select.json +35 -25
- package/data/slider.json +1 -1
- package/data/spinner.json +3 -4
- package/data/step-guide.json +130 -0
- package/data/switch.json +66 -6
- package/data/table.json +293 -0
- package/data/tag.json +68 -6
- package/data/textarea.json +1 -1
- package/data/toggle.json +2 -2
- package/data/tooltip.json +16 -3
- package/data/vertical-tab.json +220 -3
- package/package.json +27 -25
- package/bin/instructions.d.ts +0 -1
- package/bin/instructions.js +0 -14
- package/bin/tools/getComponentHtml.d.ts +0 -3
- package/bin/tools/getComponentHtml.js +0 -30
package/bin/server.d.ts
CHANGED
package/bin/server.js
CHANGED
|
@@ -1,31 +1,37 @@
|
|
|
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
2
|
/**
|
|
7
3
|
* MCP 서버 진입점 — 모든 데이터 소유, tool 등록, transport 연결
|
|
8
4
|
*
|
|
9
5
|
* 이 파일만 Action(부수효과)을 포함한다. tool 핸들러는 모두 Calculation.
|
|
10
6
|
*/
|
|
11
|
-
|
|
12
|
-
|
|
7
|
+
// biome-ignore-all lint/complexity/useLiteralKeys: descriptions['key'] 형태가 tool 이름과 일관성 (Story 5.8 scope 외)
|
|
8
|
+
// biome-ignore-all lint/style/noMagicNumbers: 기존 코드의 zod .max(N) 등 (Story 5.8 scope 외)
|
|
9
|
+
// biome-ignore-all lint/complexity/noExcessiveCognitiveComplexity: main() 진입점 (Story 5.8 scope 외, 별도 refactor 스토리)
|
|
10
|
+
var __importDefault = (this && this.__importDefault) || function (mod) {
|
|
11
|
+
return (mod && mod.__esModule) ? mod : { "default": mod };
|
|
12
|
+
};
|
|
13
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
14
|
+
const node_crypto_1 = require("node:crypto");
|
|
13
15
|
const mcp_js_1 = require("@modelcontextprotocol/sdk/server/mcp.js");
|
|
14
16
|
const stdio_js_1 = require("@modelcontextprotocol/sdk/server/stdio.js");
|
|
17
|
+
const fs_1 = __importDefault(require("fs"));
|
|
18
|
+
const path_1 = __importDefault(require("path"));
|
|
15
19
|
const zod_1 = require("zod");
|
|
16
|
-
const dataLoader_js_1 = require("./utils/dataLoader.js");
|
|
17
|
-
const logger_js_1 = require("./utils/logger.js");
|
|
18
|
-
const listComponents_js_1 = require("./tools/listComponents.js");
|
|
19
|
-
const searchComponent_js_1 = require("./tools/searchComponent.js");
|
|
20
20
|
const getComponentProps_js_1 = require("./tools/getComponentProps.js");
|
|
21
|
-
const
|
|
22
|
-
const
|
|
21
|
+
const getDesignTokens_js_1 = require("./tools/getDesignTokens.js");
|
|
22
|
+
const listComponents_js_1 = require("./tools/listComponents.js");
|
|
23
|
+
const listCompositionOverrides_js_1 = require("./tools/listCompositionOverrides.js");
|
|
23
24
|
const listIcons_js_1 = require("./tools/listIcons.js");
|
|
24
|
-
const
|
|
25
|
+
const ping_js_1 = require("./tools/ping.js");
|
|
25
26
|
const renderToHtml_js_1 = require("./tools/renderToHtml.js");
|
|
26
|
-
const
|
|
27
|
-
const
|
|
27
|
+
const searchComponent_js_1 = require("./tools/searchComponent.js");
|
|
28
|
+
const searchIcon_js_1 = require("./tools/searchIcon.js");
|
|
29
|
+
const validateHtml_js_1 = require("./tools/validateHtml.js");
|
|
30
|
+
const dataLoader_js_1 = require("./utils/dataLoader.js");
|
|
28
31
|
const domEnvironment_js_1 = require("./utils/domEnvironment.js");
|
|
32
|
+
const logger_js_1 = require("./utils/logger.js");
|
|
33
|
+
const response_js_1 = require("./utils/response.js");
|
|
34
|
+
const version_js_1 = require("./version.js");
|
|
29
35
|
// ── definitions/ 로딩 헬퍼 (Action) ─────────────────────────────────────────
|
|
30
36
|
/** tool-definitions.json을 1회 로딩하여 descriptions + capabilities를 분리 반환 */
|
|
31
37
|
const loadToolDefinitions = (definitionsDir) => {
|
|
@@ -47,7 +53,7 @@ const loadToolDefinitions = (definitionsDir) => {
|
|
|
47
53
|
}
|
|
48
54
|
return { descriptions, capabilities };
|
|
49
55
|
};
|
|
50
|
-
/** rules.json을 로딩하여 flat
|
|
56
|
+
/** rules.json을 로딩하여 flat 배열 + pingReminder 반환 */
|
|
51
57
|
const loadRules = (definitionsDir) => {
|
|
52
58
|
const raw = fs_1.default.readFileSync(path_1.default.join(definitionsDir, 'rules.json'), 'utf-8');
|
|
53
59
|
const data = JSON.parse(raw);
|
|
@@ -58,30 +64,34 @@ const loadRules = (definitionsDir) => {
|
|
|
58
64
|
'cdn',
|
|
59
65
|
'react',
|
|
60
66
|
'forbidden',
|
|
67
|
+
'composition',
|
|
68
|
+
'fontFamily',
|
|
61
69
|
'customArea',
|
|
62
70
|
'compliance',
|
|
63
71
|
'category',
|
|
64
72
|
];
|
|
65
|
-
const
|
|
73
|
+
const flat = [];
|
|
66
74
|
for (const key of RULE_KEYS) {
|
|
67
75
|
const arr = data[key];
|
|
68
76
|
if (!Array.isArray(arr)) {
|
|
69
77
|
throw new Error(`rules.json에 ${key} 배열이 없습니다`);
|
|
70
78
|
}
|
|
71
|
-
|
|
79
|
+
flat.push(...arr);
|
|
72
80
|
}
|
|
73
|
-
|
|
81
|
+
const pingReminder = typeof data.pingReminder === 'string' ? data.pingReminder : '';
|
|
82
|
+
return { flat, pingReminder };
|
|
74
83
|
};
|
|
75
84
|
// ── 진입점 ───────────────────────────────────────────────────────────────────
|
|
76
85
|
const main = async () => {
|
|
77
86
|
const definitionsDir = path_1.default.resolve(__dirname, 'definitions');
|
|
78
87
|
// ── definitions/ 로딩 (main() 안에서 실행하여 catch에 포함) ──
|
|
79
88
|
const { descriptions, capabilities } = loadToolDefinitions(definitionsDir);
|
|
80
|
-
const rules = loadRules(definitionsDir);
|
|
89
|
+
const { flat: rules, pingReminder } = loadRules(definitionsDir);
|
|
81
90
|
const instructions = (0, dataLoader_js_1.loadInstructions)(definitionsDir);
|
|
82
91
|
const complianceRules = (0, dataLoader_js_1.loadComplianceRules)(definitionsDir);
|
|
92
|
+
const jsApiMap = (0, dataLoader_js_1.loadJsApi)(definitionsDir);
|
|
83
93
|
// ── 데이터 로딩 ──
|
|
84
|
-
const componentMap = (0, dataLoader_js_1.loadComponentsFromDir)(dataLoader_js_1.DEFAULT_DATA_DIR);
|
|
94
|
+
const { map: componentMap, compositionOverrides } = (0, dataLoader_js_1.loadComponentsFromDir)(dataLoader_js_1.DEFAULT_DATA_DIR);
|
|
85
95
|
const { cdn: cdnMeta, icon: iconMeta } = (0, dataLoader_js_1.loadMeta)(dataLoader_js_1.DEFAULT_DATA_DIR);
|
|
86
96
|
const iconData = (0, dataLoader_js_1.loadIconData)(dataLoader_js_1.DEFAULT_DATA_DIR);
|
|
87
97
|
const tokenData = (0, dataLoader_js_1.loadTokenData)(dataLoader_js_1.DEFAULT_DATA_DIR);
|
|
@@ -89,6 +99,16 @@ const main = async () => {
|
|
|
89
99
|
// eslint-disable-next-line @typescript-eslint/no-require-imports, @typescript-eslint/no-var-requires
|
|
90
100
|
const bundle = require(bundlePath);
|
|
91
101
|
logger_js_1.logger.info('컴포넌트 번들 로딩 완료');
|
|
102
|
+
// 아이콘 번들 로딩 — renderToHtml에서 아이콘 이름 문자열을 실제 React 컴포넌트로 resolve
|
|
103
|
+
let iconBundle = {};
|
|
104
|
+
try {
|
|
105
|
+
// eslint-disable-next-line @typescript-eslint/no-require-imports, @typescript-eslint/no-var-requires
|
|
106
|
+
iconBundle = require('@ncds/ui-admin-icon');
|
|
107
|
+
logger_js_1.logger.info('아이콘 번들 로딩 완료');
|
|
108
|
+
}
|
|
109
|
+
catch {
|
|
110
|
+
logger_js_1.logger.warn('아이콘 번들 로딩 실패 — 아이콘 resolve 비활성화');
|
|
111
|
+
}
|
|
92
112
|
// ── DOM + React 런타임 초기화 (유틸로 추출 § 3.9) ──
|
|
93
113
|
const reactRuntime = (0, domEnvironment_js_1.setupDomEnvironment)();
|
|
94
114
|
logger_js_1.logger.info('DOM + React 런타임 초기화 완료');
|
|
@@ -99,23 +119,35 @@ const main = async () => {
|
|
|
99
119
|
const iconSummary = (0, listIcons_js_1.buildIconSummary)(iconData);
|
|
100
120
|
const listComponentsResponse = (0, listComponents_js_1.buildListComponentsResponse)(groupedComponents);
|
|
101
121
|
const listIconsResponse = (0, listIcons_js_1.buildListIconsResponse)(iconSummary);
|
|
122
|
+
const listCompositionOverridesResponse = (0, listCompositionOverrides_js_1.buildListCompositionOverridesResponse)(compositionOverrides, componentMap);
|
|
102
123
|
// ── MCP 서버 생성 + tool 등록 ──
|
|
103
124
|
const server = new mcp_js_1.McpServer({ name: 'ncds-ui-admin', version: version_js_1.VERSION }, { instructions });
|
|
104
|
-
|
|
105
|
-
|
|
125
|
+
// ping 호출 추적 — 미호출 시 다른 tool 응답에 핵심 rules 주입 (세션 레벨 상태, let 의도적 사용)
|
|
126
|
+
let pinged = false;
|
|
127
|
+
const withPingReminder = (response) => {
|
|
128
|
+
if (pinged || !pingReminder)
|
|
129
|
+
return response;
|
|
130
|
+
return (0, response_js_1.appendPingReminder)(response, pingReminder);
|
|
131
|
+
};
|
|
132
|
+
server.registerTool('ping', { description: descriptions['ping'] }, () => {
|
|
133
|
+
pinged = true;
|
|
134
|
+
return (0, ping_js_1.ping)({ componentMap, cdnMeta, iconMeta, version: version_js_1.VERSION, rules, capabilities });
|
|
135
|
+
});
|
|
136
|
+
server.registerTool('list_icons', { description: descriptions['list_icons'] }, () => withPingReminder((0, listIcons_js_1.listIcons)(listIconsResponse)));
|
|
106
137
|
server.registerTool('search_icon', {
|
|
107
138
|
description: descriptions['search_icon'],
|
|
108
139
|
inputSchema: { query: zod_1.z.string().describe('Search keyword (e.g. "search", "alert", "arrow")') },
|
|
109
|
-
}, ({ query }) => (0, searchIcon_js_1.searchIcon)(iconData, query));
|
|
110
|
-
server.registerTool('list_components', { description: descriptions['list_components'] }, () => (0, listComponents_js_1.listComponents)(listComponentsResponse));
|
|
140
|
+
}, ({ query }) => withPingReminder((0, searchIcon_js_1.searchIcon)(iconData, query)));
|
|
141
|
+
server.registerTool('list_components', { description: descriptions['list_components'] }, () => withPingReminder((0, listComponents_js_1.listComponents)(listComponentsResponse)));
|
|
142
|
+
server.registerTool('list_composition_overrides', { description: descriptions['list_composition_overrides'] }, () => withPingReminder((0, listCompositionOverrides_js_1.listCompositionOverrides)(listCompositionOverridesResponse)));
|
|
111
143
|
server.registerTool('search_component', {
|
|
112
144
|
description: descriptions['search_component'],
|
|
113
145
|
inputSchema: { query: zod_1.z.string().describe('Search keyword (Korean/English, case-insensitive)') },
|
|
114
|
-
}, ({ query }) => (0, searchComponent_js_1.searchComponent)(componentMap, query));
|
|
146
|
+
}, ({ query }) => withPingReminder((0, searchComponent_js_1.searchComponent)(componentMap, query)));
|
|
115
147
|
server.registerTool('get_component_props', {
|
|
116
148
|
description: descriptions['get_component_props'],
|
|
117
149
|
inputSchema: { name: zod_1.z.string().min(1).describe('Component name (e.g. "button", "password-input")') },
|
|
118
|
-
}, ({ name }) => (0, getComponentProps_js_1.getComponentProps)(componentMap, name));
|
|
150
|
+
}, ({ name }) => withPingReminder((0, getComponentProps_js_1.getComponentProps)(componentMap, name)));
|
|
119
151
|
server.registerTool('validate_html', {
|
|
120
152
|
description: descriptions['validate_html'],
|
|
121
153
|
inputSchema: {
|
|
@@ -124,7 +156,7 @@ const main = async () => {
|
|
|
124
156
|
.min(1)
|
|
125
157
|
.describe('HTML markup to validate (e.g. \'<button class="ncua-btn ncua-btn--primary"></button>\')'),
|
|
126
158
|
},
|
|
127
|
-
}, ({ html }) => (0, validateHtml_js_1.validateHtml)({ componentMap, rootClassMap, html, cdnMeta, tokenData, complianceRules, tokenValueMap }));
|
|
159
|
+
}, ({ html }) => withPingReminder((0, validateHtml_js_1.validateHtml)({ componentMap, rootClassMap, html, cdnMeta, tokenData, complianceRules, tokenValueMap })));
|
|
128
160
|
server.registerTool('render_to_html', {
|
|
129
161
|
description: descriptions['render_to_html'],
|
|
130
162
|
inputSchema: {
|
|
@@ -134,17 +166,49 @@ const main = async () => {
|
|
|
134
166
|
.optional()
|
|
135
167
|
.describe('Component props (e.g. { label: "OK", hierarchy: "primary" })'),
|
|
136
168
|
},
|
|
137
|
-
},
|
|
138
|
-
// MCP SDK의 zod 추론 타입이 Record<string, unknown> | undefined와 미스매치하여 as 필요
|
|
139
|
-
({ name, props }) => (0, renderToHtml_js_1.renderToHtml)({
|
|
169
|
+
}, ({ name, props }) => withPingReminder((0, renderToHtml_js_1.renderToHtml)({
|
|
140
170
|
componentMap,
|
|
141
171
|
bundle,
|
|
172
|
+
iconBundle,
|
|
142
173
|
cdnMeta,
|
|
143
174
|
iconMeta,
|
|
144
175
|
reactRuntime,
|
|
176
|
+
jsApiMap,
|
|
145
177
|
name,
|
|
146
178
|
props: props,
|
|
147
|
-
|
|
179
|
+
instanceId: (0, node_crypto_1.randomUUID)().slice(0, 6),
|
|
180
|
+
})));
|
|
181
|
+
server.registerTool('render_to_html_batch', {
|
|
182
|
+
description: descriptions['render_to_html_batch'],
|
|
183
|
+
inputSchema: {
|
|
184
|
+
components: zod_1.z
|
|
185
|
+
.array(zod_1.z.object({
|
|
186
|
+
name: zod_1.z.string().min(1).describe('Component name'),
|
|
187
|
+
props: zod_1.z.record(zod_1.z.string(), zod_1.z.unknown()).optional().describe('Component props'),
|
|
188
|
+
}))
|
|
189
|
+
.min(1)
|
|
190
|
+
.max(30)
|
|
191
|
+
.describe('Array of components to render (max 30)'),
|
|
192
|
+
},
|
|
193
|
+
}, ({ components }) => {
|
|
194
|
+
const results = components.map(({ name, props }) => (0, renderToHtml_js_1.renderToHtml)({
|
|
195
|
+
componentMap,
|
|
196
|
+
bundle,
|
|
197
|
+
iconBundle,
|
|
198
|
+
cdnMeta,
|
|
199
|
+
iconMeta,
|
|
200
|
+
reactRuntime,
|
|
201
|
+
jsApiMap,
|
|
202
|
+
name,
|
|
203
|
+
props: props,
|
|
204
|
+
instanceId: (0, node_crypto_1.randomUUID)().slice(0, 6),
|
|
205
|
+
}));
|
|
206
|
+
const parsed = results.map((r) => {
|
|
207
|
+
const text = r.content[0].text;
|
|
208
|
+
return r.isError ? { error: JSON.parse(text) } : JSON.parse(text);
|
|
209
|
+
});
|
|
210
|
+
return withPingReminder((0, response_js_1.successResponse)(parsed));
|
|
211
|
+
});
|
|
148
212
|
server.registerTool('get_design_tokens', {
|
|
149
213
|
description: descriptions['get_design_tokens'],
|
|
150
214
|
inputSchema: {
|
|
@@ -153,7 +217,7 @@ const main = async () => {
|
|
|
153
217
|
.optional()
|
|
154
218
|
.describe('Token category filter (e.g. "color", "typography", "shadow", "spacing"). Omit to get all tokens.'),
|
|
155
219
|
},
|
|
156
|
-
}, ({ category }) => (0, getDesignTokens_js_1.getDesignTokens)({ tokenData, category }));
|
|
220
|
+
}, ({ category }) => withPingReminder((0, getDesignTokens_js_1.getDesignTokens)({ tokenData, category })));
|
|
157
221
|
const transport = new stdio_js_1.StdioServerTransport();
|
|
158
222
|
await server.connect(transport);
|
|
159
223
|
logger_js_1.logger.info(`NCUA MCP 서버 시작됨 (ncds-ui-admin v${version_js_1.VERSION})`);
|
package/bin/server.mjs
CHANGED
|
File without changes
|
|
@@ -0,0 +1,18 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Story 5.10: editor 외부 분기 모듈
|
|
3
|
+
*
|
|
4
|
+
* froala 기반 에디터(`@ncds/editor-html`)는 ui-admin 외부 패키지라
|
|
5
|
+
* ui-admin 흐름(componentMap / data/{name}.json / cdnAssets) 을 거치지 않고
|
|
6
|
+
* 본 모듈에서 응답을 직접 조립한다. ui-admin 코드 미수정 → 5-8/5-9 회귀 위험 0.
|
|
7
|
+
*
|
|
8
|
+
* 호출 진입점: `renderToHtml` 함수 시작부 (Task 1 결정).
|
|
9
|
+
*/
|
|
10
|
+
import { type McpToolResponse } from '../../utils/response.js';
|
|
11
|
+
/** `normalizeName` 적용된 컴포넌트 이름이 editor alias 인지 검사 */
|
|
12
|
+
declare const isEditorAlias: (normalizedName: string) => boolean;
|
|
13
|
+
/**
|
|
14
|
+
* editor 응답 조립 — ui-admin 흐름 우회.
|
|
15
|
+
* 5-9 helper(`mergeCdnDefaults` / `stripFunctionProps`) import 재사용 (코드 중복 0).
|
|
16
|
+
*/
|
|
17
|
+
declare const buildEditorResponse: (props: Record<string, unknown>, instanceId: string) => McpToolResponse;
|
|
18
|
+
export { buildEditorResponse, isEditorAlias };
|
|
@@ -0,0 +1,88 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
/**
|
|
3
|
+
* Story 5.10: editor 외부 분기 모듈
|
|
4
|
+
*
|
|
5
|
+
* froala 기반 에디터(`@ncds/editor-html`)는 ui-admin 외부 패키지라
|
|
6
|
+
* ui-admin 흐름(componentMap / data/{name}.json / cdnAssets) 을 거치지 않고
|
|
7
|
+
* 본 모듈에서 응답을 직접 조립한다. ui-admin 코드 미수정 → 5-8/5-9 회귀 위험 0.
|
|
8
|
+
*
|
|
9
|
+
* 호출 진입점: `renderToHtml` 함수 시작부 (Task 1 결정).
|
|
10
|
+
*/
|
|
11
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
12
|
+
exports.isEditorAlias = exports.buildEditorResponse = void 0;
|
|
13
|
+
const editor_js_1 = require("../../definitions/external/editor.js");
|
|
14
|
+
const response_js_1 = require("../../utils/response.js");
|
|
15
|
+
const renderToHtml_js_1 = require("../renderToHtml.js");
|
|
16
|
+
/** editor 컴포넌트로 매칭할 alias 집합 (Task 1 결정 — 보수적 5개) */
|
|
17
|
+
const EDITOR_ALIASES = new Set(['editor', 'ncds-editor', 'ncdseditor', 'ncuaeditor', 'editor-html']);
|
|
18
|
+
/** `normalizeName` 적용된 컴포넌트 이름이 editor alias 인지 검사 */
|
|
19
|
+
const isEditorAlias = (normalizedName) => EDITOR_ALIASES.has(normalizedName);
|
|
20
|
+
exports.isEditorAlias = isEditorAlias;
|
|
21
|
+
/** camelCase → kebab-case (heightMin → height-min) */
|
|
22
|
+
const camelToKebab = (s) => s.replace(/[A-Z]/g, (m) => `-${m.toLowerCase()}`);
|
|
23
|
+
/** HTML attribute 값 escape (` " ` / ` & ` / ` < ` / ` > `) */
|
|
24
|
+
const escapeHtmlAttr = (s) => s.replace(/&/g, '&').replace(/"/g, '"').replace(/</g, '<').replace(/>/g, '>');
|
|
25
|
+
/**
|
|
26
|
+
* options → data-* 속성 직렬화.
|
|
27
|
+
* - camelCase → kebab-case
|
|
28
|
+
* - 값 String(v) (boolean → "true"/"false", number → 숫자 문자열)
|
|
29
|
+
* - null / undefined / function skip (함수는 stripFunctionProps 로 사전 제거되지만 방어적 추가 필터)
|
|
30
|
+
*/
|
|
31
|
+
const serializeDataAttributes = (options) => Object.entries(options)
|
|
32
|
+
.filter(([, v]) => v !== null && v !== undefined && typeof v !== 'function')
|
|
33
|
+
.map(([k, v]) => `data-${camelToKebab(k)}="${escapeHtmlAttr(String(v))}"`)
|
|
34
|
+
.join(' ');
|
|
35
|
+
/**
|
|
36
|
+
* patternD: textarea + data-* + element-only init script.
|
|
37
|
+
* 5-8 의 buildPatternA/B/C 와 동일 시그니처 `(id, className, options) => string`.
|
|
38
|
+
* patternA/B/C 와 차이:
|
|
39
|
+
* - 컨테이너: `<div>` → `<textarea>` (froala 가 textarea 를 in-place 변환)
|
|
40
|
+
* - options 위치: `new X(wrapper, options)` 두번째 인자 → textarea 의 `data-*` 속성
|
|
41
|
+
* - 글로벌: `ncua.X` → `window.NcdsEditor`
|
|
42
|
+
* - 호출 위치: ui-admin 의 `PATTERN_BY_COMPONENT` 분기 → 외부 분기 모듈 안 (이 함수)
|
|
43
|
+
*/
|
|
44
|
+
const buildPatternD = (id, className, options) => {
|
|
45
|
+
const dataAttrs = serializeDataAttributes(options);
|
|
46
|
+
const tagBody = dataAttrs ? `id="${id}" ${dataAttrs}` : `id="${id}"`;
|
|
47
|
+
return [
|
|
48
|
+
`<textarea ${tagBody}></textarea>`,
|
|
49
|
+
'<script>',
|
|
50
|
+
" document.addEventListener('DOMContentLoaded', function () {",
|
|
51
|
+
` new window.${className}(document.querySelector('#${id}'));`,
|
|
52
|
+
' });',
|
|
53
|
+
'</script>',
|
|
54
|
+
].join('\n');
|
|
55
|
+
};
|
|
56
|
+
/**
|
|
57
|
+
* editor 응답 조립 — ui-admin 흐름 우회.
|
|
58
|
+
* 5-9 helper(`mergeCdnDefaults` / `stripFunctionProps`) import 재사용 (코드 중복 0).
|
|
59
|
+
*/
|
|
60
|
+
const buildEditorResponse = (props, instanceId) => {
|
|
61
|
+
// 5-9: 함수 prop 사전 제거 + cdnDefaults deep merge (사용자 props 우선)
|
|
62
|
+
const cleanedProps = (0, renderToHtml_js_1.stripFunctionProps)(props);
|
|
63
|
+
const { merged, defaultsApplied } = (0, renderToHtml_js_1.mergeCdnDefaults)('editor', editor_js_1.editorDefinition.cdnDefaults, cleanedProps);
|
|
64
|
+
const id = `ed-${instanceId}`;
|
|
65
|
+
const html = buildPatternD(id, editor_js_1.editorDefinition.className, merged);
|
|
66
|
+
// cdnDefaults 적용된 키 → defaultsUsed 응답
|
|
67
|
+
const defaultsUsed = Object.fromEntries(defaultsApplied.map((key) => [key, editor_js_1.editorDefinition.cdnDefaults[key]]));
|
|
68
|
+
return (0, response_js_1.successResponse)({
|
|
69
|
+
html,
|
|
70
|
+
component: editor_js_1.editorDefinition.name,
|
|
71
|
+
exportName: editor_js_1.editorDefinition.className,
|
|
72
|
+
importPath: '@ncds/editor-html',
|
|
73
|
+
appliedProps: merged,
|
|
74
|
+
...(Object.keys(defaultsUsed).length > 0 && { defaultsUsed }),
|
|
75
|
+
js: {
|
|
76
|
+
required: true,
|
|
77
|
+
description: '이 컴포넌트는 인터랙션을 위해 CDN JS가 필요합니다',
|
|
78
|
+
api: {
|
|
79
|
+
className: editor_js_1.editorDefinition.className,
|
|
80
|
+
methods: editor_js_1.editorDefinition.methods,
|
|
81
|
+
},
|
|
82
|
+
},
|
|
83
|
+
cdn: editor_js_1.editorDefinition.cdn,
|
|
84
|
+
dataVersion: { '@ncds/editor': editor_js_1.editorDefinition.cdn.version },
|
|
85
|
+
react: { notes: 'React not supported in editor (vanilla JS only)' },
|
|
86
|
+
});
|
|
87
|
+
};
|
|
88
|
+
exports.buildEditorResponse = buildEditorResponse;
|
|
@@ -0,0 +1,19 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Story 6.1: step-guide 외부 분기 모듈
|
|
3
|
+
*
|
|
4
|
+
* vanilla JS 함수 기반 가이드 투어(`step-guide`)는 ui-admin 외부 패키지이며
|
|
5
|
+
* 5-10 editor와 동일 패턴으로 ui-admin 흐름을 우회하고 본 모듈에서 응답을 직접 조립한다.
|
|
6
|
+
* 핵심 차이 — step-guide는 함수 호출(`window.stepGuide({el, options})`) 형식이라
|
|
7
|
+
* 5-10 patternD(생성자 호출 `new window.NcdsEditor(querySelector)`)와 다른 init 스니펫 패턴 신설.
|
|
8
|
+
*
|
|
9
|
+
* 호출 진입점: `renderToHtml` 함수 시작부 (editor 분기 다음).
|
|
10
|
+
*/
|
|
11
|
+
import { type McpToolResponse } from '../../utils/response.js';
|
|
12
|
+
/** `normalizeName` 적용된 컴포넌트 이름이 step-guide alias 인지 검사 */
|
|
13
|
+
declare const isStepGuideAlias: (normalizedName: string) => boolean;
|
|
14
|
+
/**
|
|
15
|
+
* step-guide 응답 조립 — ui-admin 흐름 우회.
|
|
16
|
+
* 5-9 helper(`mergeCdnDefaults` / `stripFunctionProps`) import 재사용 (코드 중복 0).
|
|
17
|
+
*/
|
|
18
|
+
declare const buildStepGuideResponse: (props: Record<string, unknown>, _instanceId: string) => McpToolResponse;
|
|
19
|
+
export { buildStepGuideResponse, isStepGuideAlias };
|
|
@@ -0,0 +1,79 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
/**
|
|
3
|
+
* Story 6.1: step-guide 외부 분기 모듈
|
|
4
|
+
*
|
|
5
|
+
* vanilla JS 함수 기반 가이드 투어(`step-guide`)는 ui-admin 외부 패키지이며
|
|
6
|
+
* 5-10 editor와 동일 패턴으로 ui-admin 흐름을 우회하고 본 모듈에서 응답을 직접 조립한다.
|
|
7
|
+
* 핵심 차이 — step-guide는 함수 호출(`window.stepGuide({el, options})`) 형식이라
|
|
8
|
+
* 5-10 patternD(생성자 호출 `new window.NcdsEditor(querySelector)`)와 다른 init 스니펫 패턴 신설.
|
|
9
|
+
*
|
|
10
|
+
* 호출 진입점: `renderToHtml` 함수 시작부 (editor 분기 다음).
|
|
11
|
+
*/
|
|
12
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
13
|
+
exports.isStepGuideAlias = exports.buildStepGuideResponse = void 0;
|
|
14
|
+
const step_guide_js_1 = require("../../definitions/external/step-guide.js");
|
|
15
|
+
const response_js_1 = require("../../utils/response.js");
|
|
16
|
+
const renderToHtml_js_1 = require("../renderToHtml.js");
|
|
17
|
+
/** step-guide 컴포넌트로 매칭할 alias 집합 (render_to_html 진입점용, 보수적 4 영문) */
|
|
18
|
+
const STEP_GUIDE_ALIASES = new Set(['step-guide', 'stepguide', 'ncdsstepguide', 'step_guide']);
|
|
19
|
+
/** `normalizeName` 적용된 컴포넌트 이름이 step-guide alias 인지 검사 */
|
|
20
|
+
const isStepGuideAlias = (normalizedName) => STEP_GUIDE_ALIASES.has(normalizedName);
|
|
21
|
+
exports.isStepGuideAlias = isStepGuideAlias;
|
|
22
|
+
/**
|
|
23
|
+
* init 스니펫 — 함수 호출 형식.
|
|
24
|
+
* 5-10 patternD(생성자 호출 `new window.NcdsEditor(querySelector)`)와 다른 새 패턴.
|
|
25
|
+
* - 컨테이너 div 없음 — step-guide는 document.body 에 overlay를 그리는 도구라 격리 div 의미 없음
|
|
26
|
+
* - `el: document.body` — step-guide의 _fetchSteps 가 _targetElement 안에서만 step.element selector를 검색하므로
|
|
27
|
+
* 사용자가 step.element에 외부 카드 selector를 명시할 수 있도록 document.body를 target으로 지정
|
|
28
|
+
* - 함수 호출: `window.stepGuide({ el: document.body, options: ... })`
|
|
29
|
+
* - JSON.stringify로 options 직렬화 (data-* 미사용)
|
|
30
|
+
*/
|
|
31
|
+
const buildInitSnippet = (options) => {
|
|
32
|
+
const optionsJson = JSON.stringify(options);
|
|
33
|
+
return [
|
|
34
|
+
'<script>',
|
|
35
|
+
" document.addEventListener('DOMContentLoaded', function () {",
|
|
36
|
+
` window.stepGuide({ el: document.body, options: ${optionsJson} });`,
|
|
37
|
+
' });',
|
|
38
|
+
'</script>',
|
|
39
|
+
].join('\n');
|
|
40
|
+
};
|
|
41
|
+
/**
|
|
42
|
+
* step-guide 응답 조립 — ui-admin 흐름 우회.
|
|
43
|
+
* 5-9 helper(`mergeCdnDefaults` / `stripFunctionProps`) import 재사용 (코드 중복 0).
|
|
44
|
+
*/
|
|
45
|
+
const buildStepGuideResponse = (props, _instanceId // 격리 div 제거로 인해 unused — 외부 분기 시그니처 일관성 위해 인자는 유지
|
|
46
|
+
) => {
|
|
47
|
+
// 5-9: 함수 prop 사전 제거 + cdnDefaults deep merge (사용자 props 우선)
|
|
48
|
+
const cleanedProps = (0, renderToHtml_js_1.stripFunctionProps)(props);
|
|
49
|
+
const { merged, defaultsApplied } = (0, renderToHtml_js_1.mergeCdnDefaults)('step-guide', step_guide_js_1.stepGuideDefinition.cdnDefaults, cleanedProps);
|
|
50
|
+
const html = buildInitSnippet(merged);
|
|
51
|
+
// cdnDefaults 적용된 키 → defaultsUsed 응답
|
|
52
|
+
const defaultsUsed = Object.fromEntries(defaultsApplied.map((key) => [
|
|
53
|
+
key,
|
|
54
|
+
step_guide_js_1.stepGuideDefinition.cdnDefaults[key],
|
|
55
|
+
]));
|
|
56
|
+
return (0, response_js_1.successResponse)({
|
|
57
|
+
html,
|
|
58
|
+
component: step_guide_js_1.stepGuideDefinition.name,
|
|
59
|
+
exportName: step_guide_js_1.stepGuideDefinition.exportName,
|
|
60
|
+
importPath: 'step-guide',
|
|
61
|
+
appliedProps: merged,
|
|
62
|
+
...(Object.keys(defaultsUsed).length > 0 && { defaultsUsed }),
|
|
63
|
+
js: {
|
|
64
|
+
required: true,
|
|
65
|
+
description: '이 컴포넌트는 인터랙션을 위해 CDN JS가 필요합니다',
|
|
66
|
+
api: {
|
|
67
|
+
exportName: step_guide_js_1.stepGuideDefinition.exportName,
|
|
68
|
+
methods: step_guide_js_1.stepGuideDefinition.methods,
|
|
69
|
+
},
|
|
70
|
+
},
|
|
71
|
+
cdn: step_guide_js_1.stepGuideDefinition.cdn,
|
|
72
|
+
dataVersion: { '@ncds/step-guide': step_guide_js_1.stepGuideDefinition.cdn.version },
|
|
73
|
+
react: {
|
|
74
|
+
notes: 'React 환경: useEffect 내 window.stepGuide({el, options}) 호출 + cleanup에서 instance.exit() 호출. ' +
|
|
75
|
+
'StrictMode 이중 마운트 회피 — cleanup 멱등성 보장 필요 (exit()가 이미 호출된 후 다시 호출되어도 안전).',
|
|
76
|
+
},
|
|
77
|
+
});
|
|
78
|
+
};
|
|
79
|
+
exports.buildStepGuideResponse = buildStepGuideResponse;
|
|
@@ -1,9 +1,9 @@
|
|
|
1
1
|
"use strict";
|
|
2
|
-
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
|
-
exports.getComponentProps = void 0;
|
|
4
2
|
/**
|
|
5
3
|
* get_component_props tool — 컴포넌트 props 스펙 반환 (순수 함수)
|
|
6
4
|
*/
|
|
5
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
6
|
+
exports.getComponentProps = void 0;
|
|
7
7
|
const dataLoader_js_1 = require("../utils/dataLoader.js");
|
|
8
8
|
const response_js_1 = require("../utils/response.js");
|
|
9
9
|
/** get_component_props tool — 컴포넌트의 props 스펙을 JSON으로 반환 */
|
|
@@ -12,6 +12,15 @@ const getComponentProps = (componentMap, name) => {
|
|
|
12
12
|
const component = (0, dataLoader_js_1.getComponent)(componentMap, normalized);
|
|
13
13
|
if (!component)
|
|
14
14
|
return (0, response_js_1.componentNotFoundResponse)(normalized);
|
|
15
|
-
return (0, response_js_1.successResponse)(
|
|
15
|
+
return (0, response_js_1.successResponse)({
|
|
16
|
+
props: component.props,
|
|
17
|
+
...(component.subComponents && { subComponents: component.subComponents }),
|
|
18
|
+
...(component.usageExamples && { usageExamples: component.usageExamples }),
|
|
19
|
+
// P8: composition overrides 에서 병합되는 필드들 — AI 가 부모-자식 관계와 권장 조합을 우선 학습
|
|
20
|
+
...(component.allowedChildren && { allowedChildren: component.allowedChildren }),
|
|
21
|
+
...(component.allowedParents && { allowedParents: component.allowedParents }),
|
|
22
|
+
...(component.canonicalExample && { canonicalExample: component.canonicalExample }),
|
|
23
|
+
...(component.canonicalExamples && { canonicalExamples: component.canonicalExamples }),
|
|
24
|
+
});
|
|
16
25
|
};
|
|
17
26
|
exports.getComponentProps = getComponentProps;
|
|
@@ -0,0 +1,61 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* list_composition_overrides tool — composition.json composition overrides 적용 현황 요약
|
|
3
|
+
*
|
|
4
|
+
* 신규 프로젝트에서 NCDS MCP 와 연결한 뒤, 어느 컴포넌트에 어떤 composition overrides 보강이
|
|
5
|
+
* 들어가 있는지 표 형태(컴포넌트별 row)로 한 번에 확인하는 용도. 응답이 크지 않도록
|
|
6
|
+
* 시나리오 키 목록 + 카운트 위주로 구성하고, 상세는 get_component_props 로 유도.
|
|
7
|
+
*
|
|
8
|
+
* 순수 함수 — server.ts 부팅 시 1회 buildListCompositionOverridesResponse() 로 사전 직렬화.
|
|
9
|
+
*/
|
|
10
|
+
import type { ComponentData } from '../types.js';
|
|
11
|
+
import type { CompositionEntry } from '../utils/dataLoader.js';
|
|
12
|
+
import { type McpToolResponse } from '../utils/response.js';
|
|
13
|
+
export interface CompositionOverrideSummary {
|
|
14
|
+
/** 컴포넌트 이름 (kebab-case) */
|
|
15
|
+
component: string;
|
|
16
|
+
/** canonicalExamples 시나리오 key 목록 — 비어있으면 [] */
|
|
17
|
+
canonicalExamples: string[];
|
|
18
|
+
/** legacy 단일 시나리오 canonicalExample 존재 여부 */
|
|
19
|
+
hasCanonicalExample: boolean;
|
|
20
|
+
/** bemClassesExtra 항목 수 */
|
|
21
|
+
bemClassesExtra: number;
|
|
22
|
+
/** allowedChildren 정의된 부모 키 목록 (예: ["Table", "Table.Header", ...]) */
|
|
23
|
+
allowedChildrenKeys: string[];
|
|
24
|
+
/** allowedParents 정의된 자식 키 목록 */
|
|
25
|
+
allowedParentsKeys: string[];
|
|
26
|
+
/** descriptionExtra 존재 여부 */
|
|
27
|
+
descriptionExtra: boolean;
|
|
28
|
+
/** aliasesExtra 항목 수 */
|
|
29
|
+
aliasesExtra: number;
|
|
30
|
+
/**
|
|
31
|
+
* 자기 카테고리 천장 대비 정규화 점수 (0.0 ~ 1.0).
|
|
32
|
+
* 1.0 = 컴포넌트 구조상 가능한 모든 보강 영역을 완료.
|
|
33
|
+
* compound 는 5 영역, non-compound 는 4 영역 (allowedChildren n/a 제외) 기준으로 정규화되어
|
|
34
|
+
* 컴포넌트 종류 무관하게 동일 척도로 비교 가능.
|
|
35
|
+
*/
|
|
36
|
+
coverageScore: number;
|
|
37
|
+
/**
|
|
38
|
+
* 컴포넌트 구조상 적용 불가한 보강 영역 목록 (예: non-compound 는 ['allowedChildren']).
|
|
39
|
+
* 비어있으면 모든 영역이 적용 가능 (compound).
|
|
40
|
+
* coverageScore 분모에서 자동 제외되어 천장이 1.0 으로 통일된다.
|
|
41
|
+
*/
|
|
42
|
+
notApplicable: string[];
|
|
43
|
+
/** coverageScore 한 줄 해석 — high/medium/low 와 강점 영역 요약 */
|
|
44
|
+
coverageNote: string;
|
|
45
|
+
}
|
|
46
|
+
export interface ListCompositionOverridesResult {
|
|
47
|
+
/** composition overrides 적용 컴포넌트 수 */
|
|
48
|
+
total: number;
|
|
49
|
+
/** 각 척도가 무엇을 의미하고 AI 의 UI 작성 정확도에 어떻게 영향을 주는지 (AI 가 본 응답을 바로 해석하도록 동봉) */
|
|
50
|
+
metrics: Record<string, string>;
|
|
51
|
+
/** coverageScore 산식 (가중치 명시) */
|
|
52
|
+
coverageScoreFormula: string;
|
|
53
|
+
/** 컴포넌트별 요약 row 배열 — coverageScore 내림차순, 동률은 컴포넌트 이름 알파벳 */
|
|
54
|
+
overrides: CompositionOverrideSummary[];
|
|
55
|
+
}
|
|
56
|
+
/** composition overrides map → 요약 결과 (순수 함수). coverageScore 내림차순 + 동률은 이름 알파벳. */
|
|
57
|
+
export declare const buildCompositionOverridesSummary: (compositionOverrides: Record<string, CompositionEntry>, componentMap: Map<string, ComponentData>) => ListCompositionOverridesResult;
|
|
58
|
+
/** server.ts 부팅 시 1회 호출 — 응답 사전 직렬화 */
|
|
59
|
+
export declare const buildListCompositionOverridesResponse: (compositionOverrides: Record<string, CompositionEntry>, componentMap: Map<string, ComponentData>) => McpToolResponse;
|
|
60
|
+
/** list_composition_overrides tool — 사전 직렬화된 응답을 그대로 반환 */
|
|
61
|
+
export declare const listCompositionOverrides: (prebuilt: McpToolResponse) => McpToolResponse;
|