@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
|
@@ -1,53 +1 @@
|
|
|
1
|
-
"use strict";
|
|
2
|
-
/**
|
|
3
|
-
* Story 5.10: editor 외부 분기 정의
|
|
4
|
-
*
|
|
5
|
-
* froala 기반 에디터(`@ncds/editor-html`)의 응답 메타·CDN URL·props·cdnDefaults·methods.
|
|
6
|
-
* `tools/external/editor.ts` 의 `buildEditorResponse` 가 본 정의를 읽어 응답 조립.
|
|
7
|
-
* (json 대신 ts — tsconfig `module: Node16` 환경에서 import 단순화. 빌드 시 자동 포함.)
|
|
8
|
-
*/
|
|
9
|
-
Object.defineProperty(exports, "__esModule", { value: true });
|
|
10
|
-
exports.editorDefinition = void 0;
|
|
11
|
-
exports.editorDefinition = {
|
|
12
|
-
name: 'editor',
|
|
13
|
-
className: 'NcdsEditor',
|
|
14
|
-
category: 'form',
|
|
15
|
-
description: 'froala 기반 WYSIWYG 에디터. textarea 를 in-place 변환.',
|
|
16
|
-
cdn: {
|
|
17
|
-
version: '0.0',
|
|
18
|
-
css: 'https://fe-sdk.cdn-nhncommerce.com/@ncds/editor/0.0/ncds-editor.css',
|
|
19
|
-
js: 'https://fe-sdk.cdn-nhncommerce.com/@ncds/editor/0.0/ncds-editor.js',
|
|
20
|
-
},
|
|
21
|
-
props: [
|
|
22
|
-
{ name: 'heightMin', type: 'number', default: 300, description: '최소 높이 (px)' },
|
|
23
|
-
{ name: 'heightMax', type: 'number', default: 600, description: '최대 높이 (px)' },
|
|
24
|
-
{ name: 'heightResize', type: 'boolean', default: false, description: '높이 조정 가능 여부' },
|
|
25
|
-
{ name: 'placeholderText', type: 'string', default: '내용을 입력하세요.', description: '플레이스홀더 텍스트' },
|
|
26
|
-
{
|
|
27
|
-
name: 'imageUploadCallback',
|
|
28
|
-
type: 'function',
|
|
29
|
-
description: '이미지 업로드 콜백 함수명 (전역 함수). 외부 분기에서 stripFunctionProps 로 자동 제외 — data-* 직렬화 대상 아님',
|
|
30
|
-
},
|
|
31
|
-
],
|
|
32
|
-
cdnDefaults: {
|
|
33
|
-
heightMin: 300,
|
|
34
|
-
heightMax: 600,
|
|
35
|
-
heightResize: false,
|
|
36
|
-
placeholderText: '내용을 입력하세요.',
|
|
37
|
-
},
|
|
38
|
-
methods: [
|
|
39
|
-
'getHTML()',
|
|
40
|
-
'setHTML(html)',
|
|
41
|
-
'getText()',
|
|
42
|
-
'focus()',
|
|
43
|
-
'blur()',
|
|
44
|
-
'enable()',
|
|
45
|
-
'disable()',
|
|
46
|
-
'destroy()',
|
|
47
|
-
'insertImage(src, alt, link)',
|
|
48
|
-
'insertLink(href, text)',
|
|
49
|
-
'insertTable(rows, cols)',
|
|
50
|
-
'on(event, handler)',
|
|
51
|
-
'off(event)',
|
|
52
|
-
],
|
|
53
|
-
};
|
|
1
|
+
"use strict";Object.defineProperty(exports,"__esModule",{value:!0}),exports.editorDefinition=void 0,exports.editorDefinition={name:"editor",className:"NcdsEditor",category:"form",description:"froala 기반 WYSIWYG 에디터. textarea 를 in-place 변환.",cdn:{version:"0.0",css:"https://fe-sdk.cdn-nhncommerce.com/@ncds/editor/0.0/ncds-editor.css",js:"https://fe-sdk.cdn-nhncommerce.com/@ncds/editor/0.0/ncds-editor.js"},props:[{name:"heightMin",type:"number",default:300,description:"최소 높이 (px)"},{name:"heightMax",type:"number",default:600,description:"최대 높이 (px)"},{name:"heightResize",type:"boolean",default:!1,description:"높이 조정 가능 여부"},{name:"placeholderText",type:"string",default:"내용을 입력하세요.",description:"플레이스홀더 텍스트"},{name:"imageUploadCallback",type:"function",description:"이미지 업로드 콜백 함수명 (전역 함수). 외부 분기에서 stripFunctionProps 로 자동 제외 — data-* 직렬화 대상 아님"}],cdnDefaults:{heightMin:300,heightMax:600,heightResize:!1,placeholderText:"내용을 입력하세요."},methods:["getHTML()","setHTML(html)","getText()","focus()","blur()","enable()","disable()","destroy()","insertImage(src, alt, link)","insertLink(href, text)","insertTable(rows, cols)","on(event, handler)","off(event)"]};
|
|
@@ -1,52 +1 @@
|
|
|
1
|
-
"use strict";
|
|
2
|
-
/**
|
|
3
|
-
* Story 6.1: step-guide 외부 분기 정의
|
|
4
|
-
*
|
|
5
|
-
* vanilla JS 함수 기반 step-by-step 가이드 투어(`step-guide`)의 응답 메타·CDN URL·props·cdnDefaults·methods.
|
|
6
|
-
* `tools/external/step-guide.ts`의 `buildStepGuideResponse`가 본 정의를 읽어 응답 조립.
|
|
7
|
-
* 5-10 editor 패턴 답습 — 단, step-guide는 함수 호출(`window.stepGuide({el, options})`) 형식.
|
|
8
|
-
* (json 대신 ts — tsconfig `module: Node16` 환경에서 import 단순화. 빌드 시 자동 포함.)
|
|
9
|
-
*/
|
|
10
|
-
Object.defineProperty(exports, "__esModule", { value: true });
|
|
11
|
-
exports.stepGuideDefinition = void 0;
|
|
12
|
-
exports.stepGuideDefinition = {
|
|
13
|
-
name: 'step-guide',
|
|
14
|
-
exportName: 'stepGuide', // CDN UMD default export = 함수 (소문자, class 아님)
|
|
15
|
-
category: 'overlays',
|
|
16
|
-
description: 'vanilla JS 함수 기반 step-by-step 가이드 투어. CDN 로드 후 window.stepGuide({el, options}) 호출.',
|
|
17
|
-
cdn: {
|
|
18
|
-
version: '2.0',
|
|
19
|
-
css: 'https://fe-sdk.cdn-nhncommerce.com/@ncds/step-guide/2.0/step-guide.min.css',
|
|
20
|
-
js: 'https://fe-sdk.cdn-nhncommerce.com/@ncds/step-guide/2.0/step-guide.min.js',
|
|
21
|
-
},
|
|
22
|
-
props: [
|
|
23
|
-
{ name: 'steps', type: 'Step[]', required: true, description: '단계 배열' },
|
|
24
|
-
{ name: 'useAnimation', type: 'boolean', default: true, description: '애니메이션 사용 여부' },
|
|
25
|
-
{
|
|
26
|
-
name: 'buttonLabel',
|
|
27
|
-
type: '{ prev, next, done, skip }',
|
|
28
|
-
description: '버튼 라벨 (v2 default: 이전/다음/완료/나중에 하기)',
|
|
29
|
-
},
|
|
30
|
-
{ name: 'hideSkip', type: 'boolean', default: true, description: '스킵 버튼 숨김 여부' },
|
|
31
|
-
{ name: 'hideStepCount', type: 'boolean', default: true, description: '스텝 카운트 숨김 여부' },
|
|
32
|
-
{
|
|
33
|
-
name: 'hideStepPrefix',
|
|
34
|
-
type: 'boolean',
|
|
35
|
-
default: false,
|
|
36
|
-
description: 'STEP N prefix 숨김 여부. 단계 1개일 때는 옵션과 무관하게 자동 생략',
|
|
37
|
-
},
|
|
38
|
-
{
|
|
39
|
-
name: 'exitOnOverlayClick',
|
|
40
|
-
type: 'boolean',
|
|
41
|
-
default: true,
|
|
42
|
-
description: '오버레이 클릭 시 종료 여부',
|
|
43
|
-
},
|
|
44
|
-
],
|
|
45
|
-
cdnDefaults: {
|
|
46
|
-
useAnimation: true,
|
|
47
|
-
hideSkip: true,
|
|
48
|
-
hideStepCount: true,
|
|
49
|
-
exitOnOverlayClick: true,
|
|
50
|
-
},
|
|
51
|
-
methods: ['goToStep(index)', 'previousStep(e)', 'nextStep(e)', 'onChange(callback)', 'onExit(callback)', 'exit(e)'],
|
|
52
|
-
};
|
|
1
|
+
"use strict";Object.defineProperty(exports,"__esModule",{value:!0}),exports.stepGuideDefinition=void 0,exports.stepGuideDefinition={name:"step-guide",exportName:"stepGuide",category:"overlays",description:"vanilla JS 함수 기반 step-by-step 가이드 투어. CDN 로드 후 window.stepGuide({el, options}) 호출.",cdn:{version:"2.0",css:"https://fe-sdk.cdn-nhncommerce.com/@ncds/step-guide/2.0/step-guide.min.css",js:"https://fe-sdk.cdn-nhncommerce.com/@ncds/step-guide/2.0/step-guide.min.js"},props:[{name:"steps",type:"Step[]",required:!0,description:"단계 배열"},{name:"useAnimation",type:"boolean",default:!0,description:"애니메이션 사용 여부"},{name:"buttonLabel",type:"{ prev, next, done, skip }",description:"버튼 라벨 (v2 default: 이전/다음/완료/나중에 하기)"},{name:"hideSkip",type:"boolean",default:!0,description:"스킵 버튼 숨김 여부"},{name:"hideStepCount",type:"boolean",default:!0,description:"스텝 카운트 숨김 여부"},{name:"hideStepPrefix",type:"boolean",default:!1,description:"STEP N prefix 숨김 여부. 단계 1개일 때는 옵션과 무관하게 자동 생략"},{name:"exitOnOverlayClick",type:"boolean",default:!0,description:"오버레이 클릭 시 종료 여부"}],cdnDefaults:{useAnimation:!0,hideSkip:!0,hideStepCount:!0,exitOnOverlayClick:!0},methods:["goToStep(index)","previousStep(e)","nextStep(e)","onChange(callback)","onExit(callback)","exit(e)"]};
|
package/bin/server.js
CHANGED
|
@@ -1,231 +1 @@
|
|
|
1
|
-
"use strict";
|
|
2
|
-
/**
|
|
3
|
-
* MCP 서버 진입점 — 모든 데이터 소유, tool 등록, transport 연결
|
|
4
|
-
*
|
|
5
|
-
* 이 파일만 Action(부수효과)을 포함한다. tool 핸들러는 모두 Calculation.
|
|
6
|
-
*/
|
|
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");
|
|
15
|
-
const mcp_js_1 = require("@modelcontextprotocol/sdk/server/mcp.js");
|
|
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"));
|
|
19
|
-
const zod_1 = require("zod");
|
|
20
|
-
const getComponentProps_js_1 = require("./tools/getComponentProps.js");
|
|
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");
|
|
24
|
-
const listIcons_js_1 = require("./tools/listIcons.js");
|
|
25
|
-
const ping_js_1 = require("./tools/ping.js");
|
|
26
|
-
const renderToHtml_js_1 = require("./tools/renderToHtml.js");
|
|
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");
|
|
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");
|
|
35
|
-
// ── definitions/ 로딩 헬퍼 (Action) ─────────────────────────────────────────
|
|
36
|
-
/** tool-definitions.json을 1회 로딩하여 descriptions + capabilities를 분리 반환 */
|
|
37
|
-
const loadToolDefinitions = (definitionsDir) => {
|
|
38
|
-
const raw = fs_1.default.readFileSync(path_1.default.resolve(definitionsDir, 'tool-definitions.json'), 'utf-8');
|
|
39
|
-
const data = JSON.parse(raw);
|
|
40
|
-
// JSON 구조 검증 — 타입 단언만으로 신뢰하지 않음 (§ 2.5)
|
|
41
|
-
const tools = data.tools;
|
|
42
|
-
if (!tools || typeof tools !== 'object') {
|
|
43
|
-
throw new Error('tool-definitions.json에 tools 키가 없거나 올바르지 않습니다');
|
|
44
|
-
}
|
|
45
|
-
const descriptions = {};
|
|
46
|
-
for (const [name, def] of Object.entries(tools)) {
|
|
47
|
-
if (def.description)
|
|
48
|
-
descriptions[name] = def.description;
|
|
49
|
-
}
|
|
50
|
-
const capabilities = data.capabilities;
|
|
51
|
-
if (!Array.isArray(capabilities)) {
|
|
52
|
-
throw new Error('tool-definitions.json에 capabilities 배열이 없습니다');
|
|
53
|
-
}
|
|
54
|
-
return { descriptions, capabilities };
|
|
55
|
-
};
|
|
56
|
-
/** rules.json을 로딩하여 flat 배열 + pingReminder 반환 */
|
|
57
|
-
const loadRules = (definitionsDir) => {
|
|
58
|
-
const raw = fs_1.default.readFileSync(path_1.default.join(definitionsDir, 'rules.json'), 'utf-8');
|
|
59
|
-
const data = JSON.parse(raw);
|
|
60
|
-
const RULE_KEYS = [
|
|
61
|
-
'workflow',
|
|
62
|
-
'componentSelection',
|
|
63
|
-
'version',
|
|
64
|
-
'cdn',
|
|
65
|
-
'react',
|
|
66
|
-
'forbidden',
|
|
67
|
-
'composition',
|
|
68
|
-
'fontFamily',
|
|
69
|
-
'customArea',
|
|
70
|
-
'compliance',
|
|
71
|
-
'category',
|
|
72
|
-
];
|
|
73
|
-
const flat = [];
|
|
74
|
-
for (const key of RULE_KEYS) {
|
|
75
|
-
const arr = data[key];
|
|
76
|
-
if (!Array.isArray(arr)) {
|
|
77
|
-
throw new Error(`rules.json에 ${key} 배열이 없습니다`);
|
|
78
|
-
}
|
|
79
|
-
flat.push(...arr);
|
|
80
|
-
}
|
|
81
|
-
const pingReminder = typeof data.pingReminder === 'string' ? data.pingReminder : '';
|
|
82
|
-
return { flat, pingReminder };
|
|
83
|
-
};
|
|
84
|
-
// ── 진입점 ───────────────────────────────────────────────────────────────────
|
|
85
|
-
const main = async () => {
|
|
86
|
-
const definitionsDir = path_1.default.resolve(__dirname, 'definitions');
|
|
87
|
-
// ── definitions/ 로딩 (main() 안에서 실행하여 catch에 포함) ──
|
|
88
|
-
const { descriptions, capabilities } = loadToolDefinitions(definitionsDir);
|
|
89
|
-
const { flat: rules, pingReminder } = loadRules(definitionsDir);
|
|
90
|
-
const instructions = (0, dataLoader_js_1.loadInstructions)(definitionsDir);
|
|
91
|
-
const complianceRules = (0, dataLoader_js_1.loadComplianceRules)(definitionsDir);
|
|
92
|
-
const jsApiMap = (0, dataLoader_js_1.loadJsApi)(definitionsDir);
|
|
93
|
-
// ── DOM + React 런타임 초기화 — 번들 로드 전에 실행해야 unifyModuleResolution 이 적용됨 ──
|
|
94
|
-
// setupDomEnvironment()가 Module._resolveFilename 을 override 하여 react / @ncds/ui-admin-icon 경로를
|
|
95
|
-
// MCP 기준으로 pin 한다. 번들 로드 이후에 실행하면 번들 내 require() 가 pin 이전 경로로 캐시되어
|
|
96
|
-
// React 인스턴스 불일치($$typeof Symbol mismatch) 가 발생한다.
|
|
97
|
-
const reactRuntime = (0, domEnvironment_js_1.setupDomEnvironment)();
|
|
98
|
-
logger_js_1.logger.info('DOM + React 런타임 초기화 완료');
|
|
99
|
-
// ── 데이터 로딩 ──
|
|
100
|
-
const { map: componentMap, compositionOverrides } = (0, dataLoader_js_1.loadComponentsFromDir)(dataLoader_js_1.DEFAULT_DATA_DIR);
|
|
101
|
-
const { cdn: cdnMeta, icon: iconMeta } = (0, dataLoader_js_1.loadMeta)(dataLoader_js_1.DEFAULT_DATA_DIR);
|
|
102
|
-
const iconData = (0, dataLoader_js_1.loadIconData)(dataLoader_js_1.DEFAULT_DATA_DIR);
|
|
103
|
-
const tokenData = (0, dataLoader_js_1.loadTokenData)(dataLoader_js_1.DEFAULT_DATA_DIR);
|
|
104
|
-
const bundlePath = path_1.default.resolve(__dirname, '..', 'bin', 'components.bundle.js');
|
|
105
|
-
// eslint-disable-next-line @typescript-eslint/no-require-imports, @typescript-eslint/no-var-requires
|
|
106
|
-
const bundle = require(bundlePath);
|
|
107
|
-
logger_js_1.logger.info('컴포넌트 번들 로딩 완료');
|
|
108
|
-
// 아이콘 번들 로딩 — renderToHtml에서 아이콘 이름 문자열을 실제 React 컴포넌트로 resolve
|
|
109
|
-
let iconBundle = {};
|
|
110
|
-
try {
|
|
111
|
-
// eslint-disable-next-line @typescript-eslint/no-require-imports, @typescript-eslint/no-var-requires
|
|
112
|
-
iconBundle = require('@ncds/ui-admin-icon');
|
|
113
|
-
logger_js_1.logger.info('아이콘 번들 로딩 완료');
|
|
114
|
-
}
|
|
115
|
-
catch {
|
|
116
|
-
logger_js_1.logger.warn('아이콘 번들 로딩 실패 — 아이콘 resolve 비활성화');
|
|
117
|
-
}
|
|
118
|
-
// ── 파생 데이터 사전 계산 (code-guide § 5.1 반복 계산 제거) ──
|
|
119
|
-
const rootClassMap = (0, validateHtml_js_1.buildRootClassMap)(componentMap);
|
|
120
|
-
const tokenValueMap = (0, validateHtml_js_1.buildTokenValueMap)(tokenData);
|
|
121
|
-
const groupedComponents = (0, listComponents_js_1.buildGroupedComponents)(componentMap);
|
|
122
|
-
const iconSummary = (0, listIcons_js_1.buildIconSummary)(iconData);
|
|
123
|
-
const listComponentsResponse = (0, listComponents_js_1.buildListComponentsResponse)(groupedComponents);
|
|
124
|
-
const listIconsResponse = (0, listIcons_js_1.buildListIconsResponse)(iconSummary);
|
|
125
|
-
const listCompositionOverridesResponse = (0, listCompositionOverrides_js_1.buildListCompositionOverridesResponse)(compositionOverrides, componentMap);
|
|
126
|
-
// ── MCP 서버 생성 + tool 등록 ──
|
|
127
|
-
const server = new mcp_js_1.McpServer({ name: 'ncds-ui-admin', version: version_js_1.VERSION }, { instructions });
|
|
128
|
-
// ping 호출 추적 — 미호출 시 다른 tool 응답에 핵심 rules 주입 (세션 레벨 상태, let 의도적 사용)
|
|
129
|
-
let pinged = false;
|
|
130
|
-
const withPingReminder = (response) => {
|
|
131
|
-
if (pinged || !pingReminder)
|
|
132
|
-
return response;
|
|
133
|
-
return (0, response_js_1.appendPingReminder)(response, pingReminder);
|
|
134
|
-
};
|
|
135
|
-
server.registerTool('ping', { description: descriptions['ping'] }, () => {
|
|
136
|
-
pinged = true;
|
|
137
|
-
return (0, ping_js_1.ping)({ componentMap, cdnMeta, iconMeta, version: version_js_1.VERSION, rules, capabilities });
|
|
138
|
-
});
|
|
139
|
-
server.registerTool('list_icons', { description: descriptions['list_icons'] }, () => withPingReminder((0, listIcons_js_1.listIcons)(listIconsResponse)));
|
|
140
|
-
server.registerTool('search_icon', {
|
|
141
|
-
description: descriptions['search_icon'],
|
|
142
|
-
inputSchema: { query: zod_1.z.string().describe('Search keyword (e.g. "search", "alert", "arrow")') },
|
|
143
|
-
}, ({ query }) => withPingReminder((0, searchIcon_js_1.searchIcon)(iconData, query)));
|
|
144
|
-
server.registerTool('list_components', { description: descriptions['list_components'] }, () => withPingReminder((0, listComponents_js_1.listComponents)(listComponentsResponse)));
|
|
145
|
-
server.registerTool('list_composition_overrides', { description: descriptions['list_composition_overrides'] }, () => withPingReminder((0, listCompositionOverrides_js_1.listCompositionOverrides)(listCompositionOverridesResponse)));
|
|
146
|
-
server.registerTool('search_component', {
|
|
147
|
-
description: descriptions['search_component'],
|
|
148
|
-
inputSchema: { query: zod_1.z.string().describe('Search keyword (Korean/English, case-insensitive)') },
|
|
149
|
-
}, ({ query }) => withPingReminder((0, searchComponent_js_1.searchComponent)(componentMap, query)));
|
|
150
|
-
server.registerTool('get_component_props', {
|
|
151
|
-
description: descriptions['get_component_props'],
|
|
152
|
-
inputSchema: { name: zod_1.z.string().min(1).describe('Component name (e.g. "button", "password-input")') },
|
|
153
|
-
}, ({ name }) => withPingReminder((0, getComponentProps_js_1.getComponentProps)(componentMap, name)));
|
|
154
|
-
server.registerTool('validate_html', {
|
|
155
|
-
description: descriptions['validate_html'],
|
|
156
|
-
inputSchema: {
|
|
157
|
-
html: zod_1.z
|
|
158
|
-
.string()
|
|
159
|
-
.min(1)
|
|
160
|
-
.describe('HTML markup to validate (e.g. \'<button class="ncua-btn ncua-btn--primary"></button>\')'),
|
|
161
|
-
},
|
|
162
|
-
}, ({ html }) => withPingReminder((0, validateHtml_js_1.validateHtml)({ componentMap, rootClassMap, html, cdnMeta, tokenData, complianceRules, tokenValueMap })));
|
|
163
|
-
server.registerTool('render_to_html', {
|
|
164
|
-
description: descriptions['render_to_html'],
|
|
165
|
-
inputSchema: {
|
|
166
|
-
name: zod_1.z.string().min(1).describe('Component name (e.g. "button", "modal", "password-input")'),
|
|
167
|
-
props: zod_1.z
|
|
168
|
-
.record(zod_1.z.string(), zod_1.z.unknown())
|
|
169
|
-
.optional()
|
|
170
|
-
.describe('Component props (e.g. { label: "OK", hierarchy: "primary" })'),
|
|
171
|
-
},
|
|
172
|
-
}, ({ name, props }) => withPingReminder((0, renderToHtml_js_1.renderToHtml)({
|
|
173
|
-
componentMap,
|
|
174
|
-
bundle,
|
|
175
|
-
iconBundle,
|
|
176
|
-
cdnMeta,
|
|
177
|
-
iconMeta,
|
|
178
|
-
reactRuntime,
|
|
179
|
-
jsApiMap,
|
|
180
|
-
name,
|
|
181
|
-
props: props,
|
|
182
|
-
instanceId: (0, node_crypto_1.randomUUID)().slice(0, 6),
|
|
183
|
-
})));
|
|
184
|
-
server.registerTool('render_to_html_batch', {
|
|
185
|
-
description: descriptions['render_to_html_batch'],
|
|
186
|
-
inputSchema: {
|
|
187
|
-
components: zod_1.z
|
|
188
|
-
.array(zod_1.z.object({
|
|
189
|
-
name: zod_1.z.string().min(1).describe('Component name'),
|
|
190
|
-
props: zod_1.z.record(zod_1.z.string(), zod_1.z.unknown()).optional().describe('Component props'),
|
|
191
|
-
}))
|
|
192
|
-
.min(1)
|
|
193
|
-
.max(30)
|
|
194
|
-
.describe('Array of components to render (max 30)'),
|
|
195
|
-
},
|
|
196
|
-
}, ({ components }) => {
|
|
197
|
-
const results = components.map(({ name, props }) => (0, renderToHtml_js_1.renderToHtml)({
|
|
198
|
-
componentMap,
|
|
199
|
-
bundle,
|
|
200
|
-
iconBundle,
|
|
201
|
-
cdnMeta,
|
|
202
|
-
iconMeta,
|
|
203
|
-
reactRuntime,
|
|
204
|
-
jsApiMap,
|
|
205
|
-
name,
|
|
206
|
-
props: props,
|
|
207
|
-
instanceId: (0, node_crypto_1.randomUUID)().slice(0, 6),
|
|
208
|
-
}));
|
|
209
|
-
const parsed = results.map((r) => {
|
|
210
|
-
const text = r.content[0].text;
|
|
211
|
-
return r.isError ? { error: JSON.parse(text) } : JSON.parse(text);
|
|
212
|
-
});
|
|
213
|
-
return withPingReminder((0, response_js_1.successResponse)(parsed));
|
|
214
|
-
});
|
|
215
|
-
server.registerTool('get_design_tokens', {
|
|
216
|
-
description: descriptions['get_design_tokens'],
|
|
217
|
-
inputSchema: {
|
|
218
|
-
category: zod_1.z
|
|
219
|
-
.string()
|
|
220
|
-
.optional()
|
|
221
|
-
.describe('Token category filter (e.g. "color", "typography", "shadow", "spacing"). Omit to get all tokens.'),
|
|
222
|
-
},
|
|
223
|
-
}, ({ category }) => withPingReminder((0, getDesignTokens_js_1.getDesignTokens)({ tokenData, category })));
|
|
224
|
-
const transport = new stdio_js_1.StdioServerTransport();
|
|
225
|
-
await server.connect(transport);
|
|
226
|
-
logger_js_1.logger.info(`NCUA MCP 서버 시작됨 (ncds-ui-admin v${version_js_1.VERSION})`);
|
|
227
|
-
};
|
|
228
|
-
main().catch((err) => {
|
|
229
|
-
process.stderr.write(`[ERROR] 서버 시작 실패: ${err instanceof Error ? err.message : String(err)}\n`);
|
|
230
|
-
process.exit(1);
|
|
231
|
-
});
|
|
1
|
+
"use strict";var e=this&&this.__importDefault||function(e){return e&&e.__esModule?e:{default:e}};Object.defineProperty(exports,"__esModule",{value:!0});const o=require("node:crypto"),n=require("@modelcontextprotocol/sdk/server/mcp.js"),r=require("@modelcontextprotocol/sdk/server/stdio.js"),t=e(require("fs")),i=e(require("path")),s=require("zod"),a=require("./tools/getComponentProps.js"),c=require("./tools/getDesignTokens.js"),p=require("./tools/listComponents.js"),l=require("./tools/listCompositionOverrides.js"),d=require("./tools/listIcons.js"),m=require("./tools/ping.js"),u=require("./tools/renderToHtml.js"),g=require("./tools/searchComponent.js"),_=require("./tools/searchIcon.js"),h=require("./tools/validateHtml.js"),b=require("./utils/dataLoader.js"),f=require("./utils/domEnvironment.js"),y=require("./utils/logger.js"),j=require("./utils/response.js"),v=require("./version.js"),T=e=>{const o=t.default.readFileSync(i.default.resolve(e,"tool-definitions.json"),"utf-8"),n=JSON.parse(o),r=n.tools;if(!r||"object"!=typeof r)throw new Error("tool-definitions.json에 tools 키가 없거나 올바르지 않습니다");const s={};for(const[e,o]of Object.entries(r))o.description&&(s[e]=o.description);const a=n.capabilities;if(!Array.isArray(a))throw new Error("tool-definitions.json에 capabilities 배열이 없습니다");return{descriptions:s,capabilities:a}},q=e=>{const o=t.default.readFileSync(i.default.join(e,"rules.json"),"utf-8"),n=JSON.parse(o),r=["workflow","componentSelection","version","cdn","react","forbidden","composition","fontFamily","customArea","compliance","category"],s=[];for(const e of r){const o=n[e];if(!Array.isArray(o))throw new Error(`rules.json에 ${e} 배열이 없습니다`);s.push(...o)}return{flat:s,pingReminder:"string"==typeof n.pingReminder?n.pingReminder:""}},R=async()=>{const e=i.default.resolve(__dirname,"definitions"),{descriptions:t,capabilities:R}=T(e),{flat:D,pingReminder:M}=q(e),S=(0,b.loadInstructions)(e),A=(0,b.loadComplianceRules)(e),C=(0,b.loadJsApi)(e),w=(0,f.setupDomEnvironment)();y.logger.info("DOM + React 런타임 초기화 완료");const{map:k,compositionOverrides:I}=(0,b.loadComponentsFromDir)(b.DEFAULT_DATA_DIR),{cdn:O,icon:z}=(0,b.loadMeta)(b.DEFAULT_DATA_DIR),E=(0,b.loadIconData)(b.DEFAULT_DATA_DIR),L=(0,b.loadTokenData)(b.DEFAULT_DATA_DIR),U=i.default.resolve(__dirname,"..","bin","components.bundle.js"),F=require(U);y.logger.info("컴포넌트 번들 로딩 완료");let N={};try{N=require("@ncds/ui-admin-icon"),y.logger.info("아이콘 번들 로딩 완료")}catch{y.logger.warn("아이콘 번들 로딩 실패 — 아이콘 resolve 비활성화")}const x=(0,h.buildRootClassMap)(k),H=(0,h.buildTokenValueMap)(L),J=(0,p.buildGroupedComponents)(k),P=(0,d.buildIconSummary)(E),V=(0,p.buildListComponentsResponse)(J),$=(0,d.buildListIconsResponse)(P),B=(0,l.buildListCompositionOverridesResponse)(I,k),K=new n.McpServer({name:"ncds-ui-admin",version:v.VERSION},{instructions:S});let G=!1;const Q=e=>G||!M?e:(0,j.appendPingReminder)(e,M);K.registerTool("ping",{description:t.ping},()=>(G=!0,(0,m.ping)({componentMap:k,cdnMeta:O,iconMeta:z,version:v.VERSION,rules:D,capabilities:R}))),K.registerTool("list_icons",{description:t.list_icons},()=>Q((0,d.listIcons)($))),K.registerTool("search_icon",{description:t.search_icon,inputSchema:{query:s.z.string().describe('Search keyword (e.g. "search", "alert", "arrow")')}},({query:e})=>Q((0,_.searchIcon)(E,e))),K.registerTool("list_components",{description:t.list_components},()=>Q((0,p.listComponents)(V))),K.registerTool("list_composition_overrides",{description:t.list_composition_overrides},()=>Q((0,l.listCompositionOverrides)(B))),K.registerTool("search_component",{description:t.search_component,inputSchema:{query:s.z.string().describe("Search keyword (Korean/English, case-insensitive)")}},({query:e})=>Q((0,g.searchComponent)(k,e))),K.registerTool("get_component_props",{description:t.get_component_props,inputSchema:{name:s.z.string().min(1).describe('Component name (e.g. "button", "password-input")')}},({name:e})=>Q((0,a.getComponentProps)(k,e))),K.registerTool("validate_html",{description:t.validate_html,inputSchema:{html:s.z.string().min(1).describe("HTML markup to validate (e.g. '<button class=\"ncua-btn ncua-btn--primary\"></button>')")}},({html:e})=>Q((0,h.validateHtml)({componentMap:k,rootClassMap:x,html:e,cdnMeta:O,tokenData:L,complianceRules:A,tokenValueMap:H}))),K.registerTool("render_to_html",{description:t.render_to_html,inputSchema:{name:s.z.string().min(1).describe('Component name (e.g. "button", "modal", "password-input")'),props:s.z.record(s.z.string(),s.z.unknown()).optional().describe('Component props (e.g. { label: "OK", hierarchy: "primary" })')}},({name:e,props:n})=>Q((0,u.renderToHtml)({componentMap:k,bundle:F,iconBundle:N,cdnMeta:O,iconMeta:z,reactRuntime:w,jsApiMap:C,name:e,props:n,instanceId:(0,o.randomUUID)().slice(0,6)}))),K.registerTool("render_to_html_batch",{description:t.render_to_html_batch,inputSchema:{components:s.z.array(s.z.object({name:s.z.string().min(1).describe("Component name"),props:s.z.record(s.z.string(),s.z.unknown()).optional().describe("Component props")})).min(1).max(30).describe("Array of components to render (max 30)")}},({components:e})=>{const n=e.map(({name:e,props:n})=>(0,u.renderToHtml)({componentMap:k,bundle:F,iconBundle:N,cdnMeta:O,iconMeta:z,reactRuntime:w,jsApiMap:C,name:e,props:n,instanceId:(0,o.randomUUID)().slice(0,6)})).map(e=>{const o=e.content[0].text;return e.isError?{error:JSON.parse(o)}:JSON.parse(o)});return Q((0,j.successResponse)(n))}),K.registerTool("get_design_tokens",{description:t.get_design_tokens,inputSchema:{category:s.z.string().optional().describe('Token category filter (e.g. "color", "typography", "shadow", "spacing"). Omit to get all tokens.')}},({category:e})=>Q((0,c.getDesignTokens)({tokenData:L,category:e})));const W=new r.StdioServerTransport;await K.connect(W),y.logger.info(`NCUA MCP 서버 시작됨 (ncds-ui-admin v${v.VERSION})`)};R().catch(e=>{process.stderr.write(`[ERROR] 서버 시작 실패: ${e instanceof Error?e.message:String(e)}\n`),process.exit(1)});
|
|
@@ -1,88 +1 @@
|
|
|
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;
|
|
1
|
+
"use strict";Object.defineProperty(exports,"__esModule",{value:!0}),exports.isEditorAlias=exports.buildEditorResponse=void 0;const e=require("../../definitions/external/editor.js"),t=require("../../utils/response.js"),i=require("../renderToHtml.js"),o=new Set(["editor","ncds-editor","ncdseditor","ncuaeditor","editor-html"]),n=e=>o.has(e);exports.isEditorAlias=n;const r=e=>e.replace(/[A-Z]/g,e=>`-${e.toLowerCase()}`),s=e=>e.replace(/&/g,"&").replace(/"/g,""").replace(/</g,"<").replace(/>/g,">"),d=e=>Object.entries(e).filter(([,e])=>null!=e&&"function"!=typeof e).map(([e,t])=>`data-${r(e)}="${s(String(t))}"`).join(" "),a=(e,t,i)=>{const o=d(i);return[`<textarea ${o?`id="${e}" ${o}`:`id="${e}"`}></textarea>`,"<script>"," document.addEventListener('DOMContentLoaded', function () {",` new window.${t}(document.querySelector('#${e}'));`," });","<\/script>"].join("\n")},c=(o,n)=>{const r=(0,i.stripFunctionProps)(o),{merged:s,defaultsApplied:d}=(0,i.mergeCdnDefaults)("editor",e.editorDefinition.cdnDefaults,r),c=a(`ed-${n}`,e.editorDefinition.className,s),l=Object.fromEntries(d.map(t=>[t,e.editorDefinition.cdnDefaults[t]]));return(0,t.successResponse)({html:c,component:e.editorDefinition.name,exportName:e.editorDefinition.className,importPath:"@ncds/editor-html",appliedProps:s,...Object.keys(l).length>0&&{defaultsUsed:l},js:{required:!0,description:"이 컴포넌트는 인터랙션을 위해 CDN JS가 필요합니다",api:{className:e.editorDefinition.className,methods:e.editorDefinition.methods}},cdn:e.editorDefinition.cdn,dataVersion:{"@ncds/editor":e.editorDefinition.cdn.version},react:{notes:"React not supported in editor (vanilla JS only)"}})};exports.buildEditorResponse=c;
|
|
@@ -1,79 +1 @@
|
|
|
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
|
+
"use strict";Object.defineProperty(exports,"__esModule",{value:!0}),exports.isStepGuideAlias=exports.buildStepGuideResponse=void 0;const e=require("../../definitions/external/step-guide.js"),t=require("../../utils/response.js"),i=require("../renderToHtml.js"),s=new Set(["step-guide","stepguide","ncdsstepguide","step_guide"]),n=e=>s.has(e);exports.isStepGuideAlias=n;const d=e=>["<script>"," document.addEventListener('DOMContentLoaded', function () {",` window.stepGuide({ el: document.body, options: ${JSON.stringify(e)} });`," });","<\/script>"].join("\n"),o=(s,n)=>{const o=(0,i.stripFunctionProps)(s),{merged:p,defaultsApplied:u}=(0,i.mergeCdnDefaults)("step-guide",e.stepGuideDefinition.cdnDefaults,o),r=d(p),c=Object.fromEntries(u.map(t=>[t,e.stepGuideDefinition.cdnDefaults[t]]));return(0,t.successResponse)({html:r,component:e.stepGuideDefinition.name,exportName:e.stepGuideDefinition.exportName,importPath:"step-guide",appliedProps:p,...Object.keys(c).length>0&&{defaultsUsed:c},js:{required:!0,description:"이 컴포넌트는 인터랙션을 위해 CDN JS가 필요합니다",api:{exportName:e.stepGuideDefinition.exportName,methods:e.stepGuideDefinition.methods}},cdn:e.stepGuideDefinition.cdn,dataVersion:{"@ncds/step-guide":e.stepGuideDefinition.cdn.version},react:{notes:"React 환경: useEffect 내 window.stepGuide({el, options}) 호출 + cleanup에서 instance.exit() 호출. StrictMode 이중 마운트 회피 — cleanup 멱등성 보장 필요 (exit()가 이미 호출된 후 다시 호출되어도 안전)."}})};exports.buildStepGuideResponse=o;
|
|
@@ -1,26 +1 @@
|
|
|
1
|
-
"use strict";
|
|
2
|
-
/**
|
|
3
|
-
* get_component_props tool — 컴포넌트 props 스펙 반환 (순수 함수)
|
|
4
|
-
*/
|
|
5
|
-
Object.defineProperty(exports, "__esModule", { value: true });
|
|
6
|
-
exports.getComponentProps = void 0;
|
|
7
|
-
const dataLoader_js_1 = require("../utils/dataLoader.js");
|
|
8
|
-
const response_js_1 = require("../utils/response.js");
|
|
9
|
-
/** get_component_props tool — 컴포넌트의 props 스펙을 JSON으로 반환 */
|
|
10
|
-
const getComponentProps = (componentMap, name) => {
|
|
11
|
-
const normalized = (0, response_js_1.normalizeName)(name);
|
|
12
|
-
const component = (0, dataLoader_js_1.getComponent)(componentMap, normalized);
|
|
13
|
-
if (!component)
|
|
14
|
-
return (0, response_js_1.componentNotFoundResponse)(normalized);
|
|
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
|
-
});
|
|
25
|
-
};
|
|
26
|
-
exports.getComponentProps = getComponentProps;
|
|
1
|
+
"use strict";Object.defineProperty(exports,"__esModule",{value:!0}),exports.getComponentProps=void 0;const e=require("../utils/dataLoader.js"),o=require("../utils/response.js"),s=(s,n)=>{const a=(0,o.normalizeName)(n),l=(0,e.getComponent)(s,a);return l?(0,o.successResponse)({props:l.props,...l.subComponents&&{subComponents:l.subComponents},...l.usageExamples&&{usageExamples:l.usageExamples},...l.allowedChildren&&{allowedChildren:l.allowedChildren},...l.allowedParents&&{allowedParents:l.allowedParents},...l.canonicalExample&&{canonicalExample:l.canonicalExample},...l.canonicalExamples&&{canonicalExamples:l.canonicalExamples}}):(0,o.componentNotFoundResponse)(a)};exports.getComponentProps=s;
|
|
@@ -1,20 +1 @@
|
|
|
1
|
-
"use strict";
|
|
2
|
-
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
|
-
exports.getDesignTokens = void 0;
|
|
4
|
-
const response_js_1 = require("../utils/response.js");
|
|
5
|
-
const getDesignTokens = ({ tokenData, category }) => {
|
|
6
|
-
if (!category) {
|
|
7
|
-
return (0, response_js_1.successResponse)(tokenData);
|
|
8
|
-
}
|
|
9
|
-
if (!tokenData.categories.includes(category)) {
|
|
10
|
-
return (0, response_js_1.errorResponse)('INVALID_TOKEN_CATEGORY', `Category '${category}' not found. Available: ${tokenData.categories.join(', ')}`, 'Use get_design_tokens without category to see all available categories and tokens.');
|
|
11
|
-
}
|
|
12
|
-
const filteredGroups = tokenData.groups.filter((group) => group.category === category);
|
|
13
|
-
const filteredCount = filteredGroups.reduce((sum, group) => sum + group.tokens.length, 0);
|
|
14
|
-
return (0, response_js_1.successResponse)({
|
|
15
|
-
totalCount: filteredCount,
|
|
16
|
-
categories: [category],
|
|
17
|
-
groups: filteredGroups,
|
|
18
|
-
});
|
|
19
|
-
};
|
|
20
|
-
exports.getDesignTokens = getDesignTokens;
|
|
1
|
+
"use strict";Object.defineProperty(exports,"__esModule",{value:!0}),exports.getDesignTokens=void 0;const e=require("../utils/response.js"),s=({tokenData:s,category:t})=>{if(!t)return(0,e.successResponse)(s);if(!s.categories.includes(t))return(0,e.errorResponse)("INVALID_TOKEN_CATEGORY",`Category '${t}' not found. Available: ${s.categories.join(", ")}`,"Use get_design_tokens without category to see all available categories and tokens.");const o=s.groups.filter(e=>e.category===t),r=o.reduce((e,s)=>e+s.tokens.length,0);return(0,e.successResponse)({totalCount:r,categories:[t],groups:o})};exports.getDesignTokens=s;
|
|
@@ -1,24 +1 @@
|
|
|
1
|
-
"use strict";
|
|
2
|
-
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
|
-
exports.listComponents = exports.buildListComponentsResponse = exports.buildGroupedComponents = void 0;
|
|
4
|
-
const response_js_1 = require("../utils/response.js");
|
|
5
|
-
/** componentMap에서 카테고리별 그룹을 사전 계산 — server.ts에서 1회 호출 */
|
|
6
|
-
const buildGroupedComponents = (componentMap) => {
|
|
7
|
-
const sorted = [...componentMap.values()]
|
|
8
|
-
.map((c) => ({ name: c.name, category: c.category, description: c.description }))
|
|
9
|
-
.sort((a, b) => a.category.localeCompare(b.category) || a.name.localeCompare(b.name));
|
|
10
|
-
const grouped = {};
|
|
11
|
-
for (const c of sorted) {
|
|
12
|
-
if (!grouped[c.category])
|
|
13
|
-
grouped[c.category] = [];
|
|
14
|
-
grouped[c.category].push({ name: c.name, description: c.description });
|
|
15
|
-
}
|
|
16
|
-
return grouped;
|
|
17
|
-
};
|
|
18
|
-
exports.buildGroupedComponents = buildGroupedComponents;
|
|
19
|
-
/** GroupedComponents를 사전 직렬화 — server.ts에서 1회 호출 */
|
|
20
|
-
const buildListComponentsResponse = (grouped) => (0, response_js_1.successResponse)(grouped);
|
|
21
|
-
exports.buildListComponentsResponse = buildListComponentsResponse;
|
|
22
|
-
/** list_components tool — 사전 직렬화된 응답을 그대로 반환 */
|
|
23
|
-
const listComponents = (prebuilt) => prebuilt;
|
|
24
|
-
exports.listComponents = listComponents;
|
|
1
|
+
"use strict";Object.defineProperty(exports,"__esModule",{value:!0}),exports.listComponents=exports.buildListComponentsResponse=exports.buildGroupedComponents=void 0;const e=require("../utils/response.js"),o=e=>{const o=[...e.values()].map(e=>({name:e.name,category:e.category,description:e.description})).sort((e,o)=>e.category.localeCompare(o.category)||e.name.localeCompare(o.name)),s={};for(const e of o)s[e.category]||(s[e.category]=[]),s[e.category].push({name:e.name,description:e.description});return s};exports.buildGroupedComponents=o;const s=o=>(0,e.successResponse)(o);exports.buildListComponentsResponse=s;const t=e=>e;exports.listComponents=t;
|