@ncds/ui-admin-mcp 1.0.0-alpha.12 → 1.0.0-alpha.14

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.
@@ -13,6 +13,7 @@
13
13
  },
14
14
  "SelectBox": {
15
15
  "className": "SelectBox",
16
+ "cdnPattern": "A",
16
17
  "constructor": "new window.ncua.SelectBox(element, config?)",
17
18
  "constructorParams": {
18
19
  "element": "HTMLElement — target container",
@@ -25,10 +26,15 @@
25
26
  "config.disabled": "boolean"
26
27
  },
27
28
  "methods": ["getValues()", "setValues(options[])", "getSelectedCount()"],
28
- "example": "const select = new window.ncua.SelectBox(document.getElementById('my-select'), {\n options: [{ id: '1', text: 'Option 1' }],\n onChange: (value) => console.log(value)\n});"
29
+ "example": "const select = new window.ncua.SelectBox(document.getElementById('my-select'), {\n options: [{ id: '1', text: 'Option 1' }],\n onChange: (value) => console.log(value)\n});",
30
+ "cdnDefaults": {
31
+ "size": "xs",
32
+ "placeholder": "선택하세요"
33
+ }
29
34
  },
30
35
  "ComboBox": {
31
36
  "className": "ComboBox",
37
+ "cdnPattern": "A",
32
38
  "constructor": "new window.ncua.ComboBox(element, config?)",
33
39
  "constructorParams": {
34
40
  "element": "HTMLElement — target container",
@@ -41,23 +47,45 @@
41
47
  "config.multiple": "boolean"
42
48
  },
43
49
  "methods": ["getValues()", "setValues(options[])", "clearInput()", "getSelectedCount()"],
44
- "example": "const combo = new window.ncua.ComboBox(document.getElementById('my-combo'), {\n options: [{ id: '1', label: 'React' }],\n onSearch: (q) => console.log(q)\n});"
50
+ "example": "const combo = new window.ncua.ComboBox(document.getElementById('my-combo'), {\n options: [{ id: '1', label: 'React' }],\n onSearch: (q) => console.log(q)\n});",
51
+ "cdnDefaults": {
52
+ "size": "xs",
53
+ "placeholder": "검색하세요"
54
+ }
45
55
  },
46
56
  "DatePicker": {
47
57
  "className": "DatePicker",
58
+ "cdnPattern": "A",
48
59
  "constructor": "new window.ncua.DatePicker(wrapper, options)",
49
60
  "constructorParams": {
50
61
  "wrapper": "HTMLElement — container element",
51
62
  "options.size": "'xs' | 'sm'",
63
+ "options.static": "boolean",
52
64
  "options.datePickerOptions": "Array<{ element: string, placeholder?: string, options: FlatpickrOptions }>",
53
65
  "options.buttons": "Array<{ text: string, period: number, unit: 'days'|'months', isCurrent: boolean }>",
54
66
  "options.onValidationError": "(error) => void"
55
67
  },
56
68
  "methods": ["getDates()", "setDate(dates: string[])", "setMultipleDates(dates: string[])"],
57
- "example": "const dp = new window.ncua.DatePicker(document.getElementById('my-dp'), {\n size: 'xs',\n datePickerOptions: [{ element: 'date-input', placeholder: 'Select date', options: { dateFormat: 'Y-m-d' } }]\n});"
69
+ "example": "const dp = new window.ncua.DatePicker(document.getElementById('my-dp'), {\n size: 'xs',\n datePickerOptions: [{ element: 'date-input', placeholder: 'Select date', options: { dateFormat: 'Y-m-d' } }]\n});",
70
+ "cdnDefaults": {
71
+ "size": "xs",
72
+ "datePickerOptions": [
73
+ {
74
+ "options": {
75
+ "mode": "single",
76
+ "static": true,
77
+ "dateFormat": "Y-m-d",
78
+ "clickOpens": true,
79
+ "allowInvalidPreload": true,
80
+ "locale": "ko"
81
+ }
82
+ }
83
+ ]
84
+ }
58
85
  },
59
86
  "Slider": {
60
87
  "className": "Slider",
88
+ "cdnPattern": "A",
61
89
  "constructor": "new window.ncua.Slider(element, options)",
62
90
  "constructorParams": {
63
91
  "element": "HTMLElement — container",
@@ -69,7 +97,13 @@
69
97
  "options.disabled": "boolean"
70
98
  },
71
99
  "methods": ["getValue()", "setValue(value)", "enable()", "disable()", "destroy()"],
72
- "example": "const slider = new window.ncua.Slider(document.getElementById('my-slider'), {\n min: 0, max: 100, value: 50,\n onChange: (v) => console.log(v)\n});"
100
+ "example": "const slider = new window.ncua.Slider(document.getElementById('my-slider'), {\n min: 0, max: 100, value: 50,\n onChange: (v) => console.log(v)\n});",
101
+ "cdnDefaults": {
102
+ "min": 0,
103
+ "max": 100,
104
+ "step": 1,
105
+ "labelPosition": "top-floating"
106
+ }
73
107
  },
74
108
  "Tab": {
75
109
  "className": "Tab",
@@ -82,6 +116,7 @@
82
116
  },
83
117
  "Tooltip": {
84
118
  "className": "Tooltip",
119
+ "cdnPattern": "C",
85
120
  "constructor": "window.ncua.Tooltip.createShort(title, content?, options?)",
86
121
  "constructorParams": {
87
122
  "title": "string",
@@ -89,11 +124,25 @@
89
124
  "options.position": "'top' | 'bottom' | 'left' | 'right' (default: 'top')"
90
125
  },
91
126
  "methods": ["getElement()", "showTooltip()", "hideTooltip()", "destroy()"],
92
- "example": "const tip = window.ncua.Tooltip.createShort('Help', 'Tooltip content');\ndocument.getElementById('container').appendChild(tip.getElement());"
127
+ "example": "const tip = window.ncua.Tooltip.createShort('Help', 'Tooltip content');\ndocument.getElementById('container').appendChild(tip.getElement());",
128
+ "cdnDefaults": {
129
+ "type": "short",
130
+ "tooltipType": "black",
131
+ "iconType": "stroke",
132
+ "iconStyle": "help-circle",
133
+ "position": "auto",
134
+ "size": "sm"
135
+ }
93
136
  },
94
137
  "Notification": {
95
138
  "className": "Notification",
139
+ "cdnPattern": "C",
96
140
  "constructor": "window.ncua.Notification.success(title, supportingText?, options?)",
141
+ "cdnDefaults": {
142
+ "type": "floating",
143
+ "color": "neutral",
144
+ "autoClose": 0
145
+ },
97
146
  "constructorParams": {
98
147
  "title": "string",
99
148
  "supportingText": "string",
@@ -105,16 +154,21 @@
105
154
  },
106
155
  "ProgressBar": {
107
156
  "className": "ProgressBar",
157
+ "cdnPattern": "C",
108
158
  "constructor": "new window.ncua.ProgressBar(options?)",
109
159
  "constructorParams": {
110
160
  "options.value": "number (0-100)",
111
161
  "options.label": "'right' | 'bottom' | 'top-float' | 'bottom-float'"
112
162
  },
113
163
  "methods": ["getElement()", "updateOptions(newOptions)", "toHTML()"],
114
- "example": "const bar = new window.ncua.ProgressBar({ value: 65, label: 'right' });\ndocument.getElementById('container').appendChild(bar.getElement());"
164
+ "example": "const bar = new window.ncua.ProgressBar({ value: 65, label: 'right' });\ndocument.getElementById('container').appendChild(bar.getElement());",
165
+ "cdnDefaults": {
166
+ "label": "right"
167
+ }
115
168
  },
116
169
  "Tag": {
117
170
  "className": "Tag",
171
+ "cdnPattern": "C",
118
172
  "constructor": "new window.ncua.Tag(options)",
119
173
  "constructorParams": {
120
174
  "options.text": "string",
@@ -123,10 +177,14 @@
123
177
  "options.onButtonClick": "() => void — close handler"
124
178
  },
125
179
  "methods": ["getElement()", "setText(text)", "setCount(count)", "destroy()"],
126
- "example": "const tag = new window.ncua.Tag({ text: 'Label', close: true, onButtonClick: () => tag.destroy() });\ndocument.getElementById('container').appendChild(tag.getElement());"
180
+ "example": "const tag = new window.ncua.Tag({ text: 'Label', close: true, onButtonClick: () => tag.destroy() });\ndocument.getElementById('container').appendChild(tag.getElement());",
181
+ "cdnDefaults": {
182
+ "size": "sm"
183
+ }
127
184
  },
128
185
  "FileInput": {
129
186
  "className": "FileInput",
187
+ "cdnPattern": "B",
130
188
  "constructor": "new window.ncua.FileInput(options)",
131
189
  "constructorParams": {
132
190
  "options.container": "HTMLElement | string — auto-append target",
@@ -135,10 +193,14 @@
135
193
  "options.disabled": "boolean"
136
194
  },
137
195
  "methods": ["getElement()", "setDisabled(boolean)", "destroy()"],
138
- "example": "const fi = new window.ncua.FileInput({\n container: 'file-container',\n onChange: (files) => console.log(files)\n});"
196
+ "example": "const fi = new window.ncua.FileInput({\n container: 'file-container',\n onChange: (files) => console.log(files)\n});",
197
+ "cdnDefaults": {
198
+ "buttonLabel": "파일 찾기"
199
+ }
139
200
  },
140
201
  "ImageFileInput": {
141
202
  "className": "ImageFileInput",
203
+ "cdnPattern": "B",
142
204
  "constructor": "new window.ncua.ImageFileInput(options)",
143
205
  "constructorParams": {
144
206
  "options.container": "HTMLElement | string — auto-append target",
@@ -147,6 +209,9 @@
147
209
  "options.acceptedFileTypes": "string[]",
148
210
  "options.maxFileSize": "number"
149
211
  },
212
+ "cdnDefaults": {
213
+ "buttonLabel": "파일 찾기"
214
+ },
150
215
  "methods": ["getElement()", "getFiles()", "clearFiles()", "setDisabled(boolean)", "destroy()"],
151
216
  "example": "const img = new window.ncua.ImageFileInput({\n container: 'image-container',\n maxFileCount: 5,\n onChange: (files) => console.log(files)\n});"
152
217
  },
package/bin/server.d.ts CHANGED
@@ -1 +1,6 @@
1
+ /**
2
+ * MCP 서버 진입점 — 모든 데이터 소유, tool 등록, transport 연결
3
+ *
4
+ * 이 파일만 Action(부수효과)을 포함한다. tool 핸들러는 모두 Calculation.
5
+ */
1
6
  export {};
package/bin/server.js CHANGED
@@ -1,32 +1,36 @@
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
- const path_1 = __importDefault(require("path"));
12
- const fs_1 = __importDefault(require("fs"));
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 validateHtml_js_1 = require("./tools/validateHtml.js");
22
- const ping_js_1 = require("./tools/ping.js");
21
+ const getDesignTokens_js_1 = require("./tools/getDesignTokens.js");
22
+ const listComponents_js_1 = require("./tools/listComponents.js");
23
23
  const listIcons_js_1 = require("./tools/listIcons.js");
24
- const searchIcon_js_1 = require("./tools/searchIcon.js");
24
+ const ping_js_1 = require("./tools/ping.js");
25
25
  const renderToHtml_js_1 = require("./tools/renderToHtml.js");
26
+ const searchComponent_js_1 = require("./tools/searchComponent.js");
27
+ const searchIcon_js_1 = require("./tools/searchIcon.js");
28
+ const validateHtml_js_1 = require("./tools/validateHtml.js");
29
+ const dataLoader_js_1 = require("./utils/dataLoader.js");
30
+ const domEnvironment_js_1 = require("./utils/domEnvironment.js");
31
+ const logger_js_1 = require("./utils/logger.js");
26
32
  const response_js_1 = require("./utils/response.js");
27
- const getDesignTokens_js_1 = require("./tools/getDesignTokens.js");
28
33
  const version_js_1 = require("./version.js");
29
- const domEnvironment_js_1 = require("./utils/domEnvironment.js");
30
34
  // ── definitions/ 로딩 헬퍼 (Action) ─────────────────────────────────────────
31
35
  /** tool-definitions.json을 1회 로딩하여 descriptions + capabilities를 분리 반환 */
32
36
  const loadToolDefinitions = (definitionsDir) => {
@@ -167,6 +171,7 @@ const main = async () => {
167
171
  jsApiMap,
168
172
  name,
169
173
  props: props,
174
+ instanceId: (0, node_crypto_1.randomUUID)().slice(0, 6),
170
175
  })));
171
176
  server.registerTool('render_to_html_batch', {
172
177
  description: descriptions['render_to_html_batch'],
@@ -191,6 +196,7 @@ const main = async () => {
191
196
  jsApiMap,
192
197
  name,
193
198
  props: props,
199
+ instanceId: (0, node_crypto_1.randomUUID)().slice(0, 6),
194
200
  }));
195
201
  const parsed = results.map((r) => {
196
202
  const text = r.content[0].text;
@@ -0,0 +1,17 @@
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
+ export declare const isEditorAlias: (normalizedName: string) => boolean;
13
+ /**
14
+ * editor 응답 조립 — ui-admin 흐름 우회.
15
+ * 5-9 helper(`mergeCdnDefaults` / `stripFunctionProps`) import 재사용 (코드 중복 0).
16
+ */
17
+ export declare const buildEditorResponse: (props: Record<string, unknown>, instanceId: string) => McpToolResponse;
@@ -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.buildEditorResponse = exports.isEditorAlias = 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, '&amp;').replace(/"/g, '&quot;').replace(/</g, '&lt;').replace(/>/g, '&gt;');
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;
@@ -1,10 +1,4 @@
1
- /**
2
- * render_to_html tool — 컴포넌트 속성을 전달하면 정확한 HTML + React 매핑을 반환 (순수 함수)
3
- *
4
- * DOM 환경과 React/ReactDOM은 server.ts에서 초기화하고 파라미터로 전달한다.
5
- * 이 파일에는 fs, path, require, let이 없다.
6
- */
7
- import type { ComponentData, ReactRuntime, JsApiInfo } from '../types.js';
1
+ import type { ComponentData, JsApiInfo, ReactRuntime } from '../types.js';
8
2
  import type { CdnMeta, IconMeta } from '../utils/dataLoader.js';
9
3
  import { type McpToolResponse } from '../utils/response.js';
10
4
  /** render_to_html tool 파라미터 */
@@ -18,6 +12,12 @@ export interface RenderToHtmlParams {
18
12
  jsApiMap: Map<string, JsApiInfo>;
19
13
  name: string;
20
14
  props?: Record<string, unknown>;
15
+ /**
16
+ * CDN init 패턴(Story 5.8) 생성 시 `<div id>` 접미사로 사용되는 고유 식별자.
17
+ * server.ts가 매 호출마다 `crypto.randomUUID().slice(0, 6)` 방식으로 생성해 주입한다.
18
+ * renderToHtml.ts는 순수 함수 원칙(파일 헤더 주석)을 위해 내부에서 랜덤을 생성하지 않는다.
19
+ */
20
+ instanceId: string;
21
21
  }
22
22
  /** render_to_html tool — props로 React 컴포넌트를 렌더링하여 HTML + React 매핑 반환 */
23
23
  export declare const renderToHtml: (params: RenderToHtmlParams) => McpToolResponse;