@ncds/ui-admin-mcp 1.0.0-alpha.24 → 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/overrides/composition.json +1 -1
- 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/data-grid.json +4 -0
- package/data/select-box.json +6 -0
- package/data/select.json +6 -0
- package/package.json +5 -2
|
@@ -1,194 +1 @@
|
|
|
1
|
-
"use strict";
|
|
2
|
-
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
|
-
exports.extractAllCss = exports.detectTokenIssues = exports.buildTokenValueMap = void 0;
|
|
4
|
-
// ── server.ts 사전 계산용 ───────────────────────────────────────────────
|
|
5
|
-
/** hex 값(lowercase) → 토큰 CSS 변수명 배열 역방향 매핑 빌드 — server.ts에서 1회 호출 */
|
|
6
|
-
const buildTokenValueMap = (tokenData) => {
|
|
7
|
-
const map = new Map();
|
|
8
|
-
for (const group of tokenData.groups) {
|
|
9
|
-
if (group.category !== 'color')
|
|
10
|
-
continue;
|
|
11
|
-
for (const token of group.tokens) {
|
|
12
|
-
const hex = token.value.toLowerCase();
|
|
13
|
-
const existing = map.get(hex);
|
|
14
|
-
if (existing)
|
|
15
|
-
existing.push(token.name);
|
|
16
|
-
else
|
|
17
|
-
map.set(hex, [token.name]);
|
|
18
|
-
}
|
|
19
|
-
}
|
|
20
|
-
return map;
|
|
21
|
-
};
|
|
22
|
-
exports.buildTokenValueMap = buildTokenValueMap;
|
|
23
|
-
// ── 상수 ─────────────────────────────────────────────────────────────────
|
|
24
|
-
const HEX_PATTERN_SOURCE = /#([0-9a-fA-F]{3,8})\b/g.source;
|
|
25
|
-
const RGB_PATTERN_SOURCE = /rgba?\(\s*(\d+)\s*,\s*(\d+)\s*,\s*(\d+)/g.source;
|
|
26
|
-
const VAR_PATTERN_SOURCE = /var\(\s*(--[a-zA-Z0-9-]+)(?:\s*,\s*[^)]+)?\s*\)/g.source;
|
|
27
|
-
// ── 순수 헬퍼 ────────────────────────────────────────────────────────────
|
|
28
|
-
const HEX_RADIX = 16;
|
|
29
|
-
const HEX_BYTE_LENGTH = 2;
|
|
30
|
-
const HEX_SHORT_LENGTH = 3;
|
|
31
|
-
const HEX_FULL_LENGTH = 6;
|
|
32
|
-
/** rgb(r,g,b) → #rrggbb hex 변환 */
|
|
33
|
-
const rgbToHex = (r, g, b) => `#${[r, g, b].map((v) => v.toString(HEX_RADIX).padStart(HEX_BYTE_LENGTH, '0')).join('')}`;
|
|
34
|
-
/** 3자리 hex를 6자리로 정규화 */
|
|
35
|
-
const normalizeHex = (hex) => {
|
|
36
|
-
const h = hex.toLowerCase().replace('#', '');
|
|
37
|
-
if (h.length === HEX_SHORT_LENGTH)
|
|
38
|
-
return `#${h[0]}${h[0]}${h[1]}${h[1]}${h[2]}${h[2]}`;
|
|
39
|
-
return `#${h.slice(0, HEX_FULL_LENGTH)}`;
|
|
40
|
-
};
|
|
41
|
-
/** var()의 prefix가 토큰 시스템에 속하는지 확인 */
|
|
42
|
-
const hasKnownTokenPrefix = (varName, prefixSet) => {
|
|
43
|
-
for (const prefix of prefixSet) {
|
|
44
|
-
if (varName.startsWith(prefix))
|
|
45
|
-
return true;
|
|
46
|
-
}
|
|
47
|
-
return false;
|
|
48
|
-
};
|
|
49
|
-
/** tokenData에 등록된 토큰명 prefix Set — 커스텀 변수 제외용 */
|
|
50
|
-
const buildTokenPrefixSet = (tokenData) => {
|
|
51
|
-
const prefixes = new Set();
|
|
52
|
-
for (const group of tokenData.groups) {
|
|
53
|
-
for (const token of group.tokens) {
|
|
54
|
-
const stripped = token.name.replace(/^--/, '');
|
|
55
|
-
const segments = stripped.split('-');
|
|
56
|
-
if (segments.length >= 2)
|
|
57
|
-
prefixes.add(`--${segments.slice(0, 2).join('-')}`);
|
|
58
|
-
if (segments.length >= 1)
|
|
59
|
-
prefixes.add(`--${segments[0]}`);
|
|
60
|
-
}
|
|
61
|
-
}
|
|
62
|
-
return prefixes;
|
|
63
|
-
};
|
|
64
|
-
/** 모든 토큰명을 Set으로 수집 */
|
|
65
|
-
const buildTokenNameSet = (tokenData) => {
|
|
66
|
-
const names = new Set();
|
|
67
|
-
for (const group of tokenData.groups) {
|
|
68
|
-
for (const token of group.tokens)
|
|
69
|
-
names.add(token.name);
|
|
70
|
-
}
|
|
71
|
-
return names;
|
|
72
|
-
};
|
|
73
|
-
/** token prefix → 카테고리 역방향 맵 — invalid_token suggestion에 카테고리 명시용 */
|
|
74
|
-
const buildPrefixCategoryMap = (tokenData) => {
|
|
75
|
-
const map = new Map();
|
|
76
|
-
for (const group of tokenData.groups) {
|
|
77
|
-
for (const token of group.tokens) {
|
|
78
|
-
const stripped = token.name.replace(/^--/, '');
|
|
79
|
-
const segments = stripped.split('-');
|
|
80
|
-
if (segments.length >= 2)
|
|
81
|
-
map.set(`--${segments.slice(0, 2).join('-')}`, group.category);
|
|
82
|
-
if (segments.length >= 1)
|
|
83
|
-
map.set(`--${segments[0]}`, group.category);
|
|
84
|
-
}
|
|
85
|
-
}
|
|
86
|
-
return map;
|
|
87
|
-
};
|
|
88
|
-
/** var 이름의 prefix에서 카테고리 추론 — 더 긴 prefix 우선 */
|
|
89
|
-
const inferCategoryFromVarName = (varName, prefixCategoryMap) => {
|
|
90
|
-
const stripped = varName.replace(/^--/, '');
|
|
91
|
-
const segments = stripped.split('-');
|
|
92
|
-
if (segments.length >= 2) {
|
|
93
|
-
const cat = prefixCategoryMap.get(`--${segments.slice(0, 2).join('-')}`);
|
|
94
|
-
if (cat)
|
|
95
|
-
return cat;
|
|
96
|
-
}
|
|
97
|
-
return prefixCategoryMap.get(`--${segments[0]}`);
|
|
98
|
-
};
|
|
99
|
-
/** inline style + <style> 블록의 CSS 텍스트를 하나로 합침 */
|
|
100
|
-
const extractAllCss = (root) => {
|
|
101
|
-
const parts = [];
|
|
102
|
-
for (const el of root.querySelectorAll('[style]')) {
|
|
103
|
-
const style = el.getAttribute('style');
|
|
104
|
-
if (style)
|
|
105
|
-
parts.push(style);
|
|
106
|
-
}
|
|
107
|
-
for (const styleEl of root.querySelectorAll('style')) {
|
|
108
|
-
parts.push(styleEl.textContent ?? '');
|
|
109
|
-
}
|
|
110
|
-
return parts.join(' ');
|
|
111
|
-
};
|
|
112
|
-
exports.extractAllCss = extractAllCss;
|
|
113
|
-
/** CSS 텍스트에서 하드코딩된 색상값(hex/rgb)을 추출 */
|
|
114
|
-
const collectHardcodedColors = (css) => {
|
|
115
|
-
const colors = [];
|
|
116
|
-
const hexPattern = new RegExp(HEX_PATTERN_SOURCE, 'g');
|
|
117
|
-
for (const match of css.matchAll(hexPattern)) {
|
|
118
|
-
colors.push({ raw: match[0], normalized: normalizeHex(match[0]) });
|
|
119
|
-
}
|
|
120
|
-
const rgbPattern = new RegExp(RGB_PATTERN_SOURCE, 'g');
|
|
121
|
-
for (const match of css.matchAll(rgbPattern)) {
|
|
122
|
-
colors.push({
|
|
123
|
-
raw: match[0],
|
|
124
|
-
normalized: rgbToHex(Number.parseInt(match[1], 10), Number.parseInt(match[2], 10), Number.parseInt(match[3], 10)),
|
|
125
|
-
});
|
|
126
|
-
}
|
|
127
|
-
return colors;
|
|
128
|
-
};
|
|
129
|
-
/** token_not_used suggestion 생성 */
|
|
130
|
-
const buildTokenSuggestion = (raw, tokenNames) => tokenNames.length === 1
|
|
131
|
-
? `Use var(${tokenNames[0]}) instead of ${raw}`
|
|
132
|
-
: `Possible tokens: ${tokenNames.map((t) => `var(${t})`).join(', ')}`;
|
|
133
|
-
// ── 검증 함수 ────────────────────────────────────────────────────────────
|
|
134
|
-
/** 하드코딩 색상 → token_not_used 에러 수집 */
|
|
135
|
-
const collectTokenNotUsedErrors = (css, tokenValueMap) => {
|
|
136
|
-
const errors = [];
|
|
137
|
-
let missing = 0;
|
|
138
|
-
for (const color of collectHardcodedColors(css)) {
|
|
139
|
-
const tokenNames = tokenValueMap.get(color.normalized);
|
|
140
|
-
if (!tokenNames)
|
|
141
|
-
continue;
|
|
142
|
-
missing++;
|
|
143
|
-
errors.push({
|
|
144
|
-
type: 'token_not_used',
|
|
145
|
-
target: color.raw,
|
|
146
|
-
component: '(inline-style)',
|
|
147
|
-
message: `Hardcoded color '${color.raw}' should use a design token.`,
|
|
148
|
-
suggestion: buildTokenSuggestion(color.raw, tokenNames),
|
|
149
|
-
});
|
|
150
|
-
}
|
|
151
|
-
return { errors, missing };
|
|
152
|
-
};
|
|
153
|
-
/** var() 참조 → invalid_token 에러 수집 + correct 카운트 */
|
|
154
|
-
const collectInvalidTokenErrors = (css, tokenNameSet, tokenPrefixSet, prefixCategoryMap) => {
|
|
155
|
-
const errors = [];
|
|
156
|
-
let correct = 0;
|
|
157
|
-
let invalid = 0;
|
|
158
|
-
const varPattern = new RegExp(VAR_PATTERN_SOURCE, 'g');
|
|
159
|
-
for (const varMatch of css.matchAll(varPattern)) {
|
|
160
|
-
const varName = varMatch[1];
|
|
161
|
-
if (!hasKnownTokenPrefix(varName, tokenPrefixSet))
|
|
162
|
-
continue;
|
|
163
|
-
if (tokenNameSet.has(varName)) {
|
|
164
|
-
correct++;
|
|
165
|
-
}
|
|
166
|
-
else {
|
|
167
|
-
invalid++;
|
|
168
|
-
const category = inferCategoryFromVarName(varName, prefixCategoryMap);
|
|
169
|
-
const suggestion = category
|
|
170
|
-
? `Call get_design_tokens('${category}') to see available ${category} tokens.`
|
|
171
|
-
: 'Call get_design_tokens to see available tokens.';
|
|
172
|
-
errors.push({
|
|
173
|
-
type: 'invalid_token',
|
|
174
|
-
target: `var(${varName})`,
|
|
175
|
-
component: '(inline-style)',
|
|
176
|
-
message: `Token '${varName}' does not exist in the design system.`,
|
|
177
|
-
suggestion,
|
|
178
|
-
});
|
|
179
|
-
}
|
|
180
|
-
}
|
|
181
|
-
return { errors, correct, invalid };
|
|
182
|
-
};
|
|
183
|
-
/** 토큰 미사용 + 무효 토큰 감지 */
|
|
184
|
-
const detectTokenIssues = (params) => {
|
|
185
|
-
const { root, tokenData, tokenValueMap } = params;
|
|
186
|
-
const allCss = extractAllCss(root);
|
|
187
|
-
const notUsed = collectTokenNotUsedErrors(allCss, tokenValueMap);
|
|
188
|
-
const invalidResult = collectInvalidTokenErrors(allCss, buildTokenNameSet(tokenData), buildTokenPrefixSet(tokenData), buildPrefixCategoryMap(tokenData));
|
|
189
|
-
return {
|
|
190
|
-
errors: [...notUsed.errors, ...invalidResult.errors],
|
|
191
|
-
tokenUsage: { correct: invalidResult.correct, missing: notUsed.missing, invalid: invalidResult.invalid },
|
|
192
|
-
};
|
|
193
|
-
};
|
|
194
|
-
exports.detectTokenIssues = detectTokenIssues;
|
|
1
|
+
"use strict";Object.defineProperty(exports,"__esModule",{value:!0}),exports.extractAllCss=exports.detectTokenIssues=exports.buildTokenValueMap=void 0;const e=e=>{const t=new Map;for(const o of e.groups)if("color"===o.category)for(const e of o.tokens){const o=e.value.toLowerCase(),s=t.get(o);s?s.push(e.name):t.set(o,[e.name])}return t};exports.buildTokenValueMap=e;const t=/#([0-9a-fA-F]{3,8})\b/g.source,o=/rgba?\(\s*(\d+)\s*,\s*(\d+)\s*,\s*(\d+)/g.source,s=/var\(\s*(--[a-zA-Z0-9-]+)(?:\s*,\s*[^)]+)?\s*\)/g.source,n=16,r=2,a=3,c=6,l=(e,t,o)=>`#${[e,t,o].map(e=>e.toString(n).padStart(2,"0")).join("")}`,i=e=>{const t=e.toLowerCase().replace("#","");return 3===t.length?`#${t[0]}${t[0]}${t[1]}${t[1]}${t[2]}${t[2]}`:`#${t.slice(0,6)}`},u=(e,t)=>{for(const o of t)if(e.startsWith(o))return!0;return!1},g=e=>{const t=new Set;for(const o of e.groups)for(const e of o.tokens){const o=e.name.replace(/^--/,"").split("-");o.length>=2&&t.add(`--${o.slice(0,2).join("-")}`),o.length>=1&&t.add(`--${o[0]}`)}return t},p=e=>{const t=new Set;for(const o of e.groups)for(const e of o.tokens)t.add(e.name);return t},f=e=>{const t=new Map;for(const o of e.groups)for(const e of o.tokens){const s=e.name.replace(/^--/,"").split("-");s.length>=2&&t.set(`--${s.slice(0,2).join("-")}`,o.category),s.length>=1&&t.set(`--${s[0]}`,o.category)}return t},d=(e,t)=>{const o=e.replace(/^--/,"").split("-");if(o.length>=2){const e=t.get(`--${o.slice(0,2).join("-")}`);if(e)return e}return t.get(`--${o[0]}`)},m=e=>{const t=[];for(const o of e.querySelectorAll("[style]")){const e=o.getAttribute("style");e&&t.push(e)}for(const o of e.querySelectorAll("style"))t.push(o.textContent??"");return t.join(" ")};exports.extractAllCss=m;const $=e=>{const s=[],n=new RegExp(t,"g");for(const t of e.matchAll(n))s.push({raw:t[0],normalized:i(t[0])});const r=new RegExp(o,"g");for(const t of e.matchAll(r))s.push({raw:t[0],normalized:l(Number.parseInt(t[1],10),Number.parseInt(t[2],10),Number.parseInt(t[3],10))});return s},h=(e,t)=>1===t.length?`Use var(${t[0]}) instead of ${e}`:`Possible tokens: ${t.map(e=>`var(${e})`).join(", ")}`,k=(e,t)=>{const o=[];let s=0;for(const n of $(e)){const e=t.get(n.normalized);e&&(s++,o.push({type:"token_not_used",target:n.raw,component:"(inline-style)",message:`Hardcoded color '${n.raw}' should use a design token.`,suggestion:h(n.raw,e)}))}return{errors:o,missing:s}},w=(e,t,o,n)=>{const r=[];let a=0,c=0;const l=new RegExp(s,"g");for(const s of e.matchAll(l)){const e=s[1];if(u(e,o))if(t.has(e))a++;else{c++;const t=d(e,n),o=t?`Call get_design_tokens('${t}') to see available ${t} tokens.`:"Call get_design_tokens to see available tokens.";r.push({type:"invalid_token",target:`var(${e})`,component:"(inline-style)",message:`Token '${e}' does not exist in the design system.`,suggestion:o})}}return{errors:r,correct:a,invalid:c}},x=e=>{const{root:t,tokenData:o,tokenValueMap:s}=e,n=m(t),r=k(n,s),a=w(n,p(o),g(o),f(o));return{errors:[...r.errors,...a.errors],tokenUsage:{correct:a.correct,missing:r.missing,invalid:a.invalid}}};exports.detectTokenIssues=x;
|
package/bin/version.d.ts
CHANGED
package/bin/version.js
CHANGED
|
@@ -1,9 +1 @@
|
|
|
1
|
-
"use strict";
|
|
2
|
-
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
|
-
exports.VERSION = void 0;
|
|
4
|
-
/**
|
|
5
|
-
* NCDS UI Admin MCP 서버가 노출하는 ui-admin 정합 기준 버전.
|
|
6
|
-
* scripts/generate-version-ts.ts 가 ui-admin/package.json 의 version 으로부터 자동 생성한다.
|
|
7
|
-
* 수동 편집 금지 — 빌드(`pnpm generate:version-ts` 또는 `pnpm build`) 시 덮어쓰여진다.
|
|
8
|
-
*/
|
|
9
|
-
exports.VERSION = '1.8.12';
|
|
1
|
+
"use strict";Object.defineProperty(exports,"__esModule",{value:!0}),exports.VERSION=void 0,exports.VERSION="1.8.14";
|
package/data/data-grid.json
CHANGED
package/data/select-box.json
CHANGED
|
@@ -145,6 +145,11 @@
|
|
|
145
145
|
"required": false,
|
|
146
146
|
"default": "선택하세요"
|
|
147
147
|
},
|
|
148
|
+
"readOnly": {
|
|
149
|
+
"type": "boolean",
|
|
150
|
+
"required": false,
|
|
151
|
+
"default": false
|
|
152
|
+
},
|
|
148
153
|
"register": {
|
|
149
154
|
"type": "object",
|
|
150
155
|
"required": false,
|
|
@@ -187,6 +192,7 @@
|
|
|
187
192
|
"ncua-selectbox--disabled",
|
|
188
193
|
"ncua-selectbox--multiple",
|
|
189
194
|
"ncua-selectbox--open",
|
|
195
|
+
"ncua-selectbox--readonly",
|
|
190
196
|
"ncua-selectbox--simple",
|
|
191
197
|
"ncua-selectbox__arrow",
|
|
192
198
|
"ncua-selectbox__arrow--up",
|
package/data/select.json
CHANGED
|
@@ -80,6 +80,11 @@
|
|
|
80
80
|
"type": "string",
|
|
81
81
|
"required": false
|
|
82
82
|
},
|
|
83
|
+
"readOnly": {
|
|
84
|
+
"type": "boolean",
|
|
85
|
+
"required": false,
|
|
86
|
+
"default": false
|
|
87
|
+
},
|
|
83
88
|
"register": {
|
|
84
89
|
"type": "object",
|
|
85
90
|
"required": false,
|
|
@@ -109,6 +114,7 @@
|
|
|
109
114
|
"bemClasses": [
|
|
110
115
|
"ncua-hint-text",
|
|
111
116
|
"ncua-select",
|
|
117
|
+
"ncua-select--readonly",
|
|
112
118
|
"ncua-select--simple",
|
|
113
119
|
"ncua-select__content",
|
|
114
120
|
"ncua-select__tag"
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@ncds/ui-admin-mcp",
|
|
3
|
-
"version": "1.0.0-alpha.
|
|
3
|
+
"version": "1.0.0-alpha.26",
|
|
4
4
|
"description": "NCDS UI Admin MCP 서버 — AI 에이전트가 NCUA 컴포넌트를 조회하고 HTML을 검증할 수 있는 MCP 서버",
|
|
5
5
|
"bin": {
|
|
6
6
|
"ncua-mcp": "./bin/server.mjs"
|
|
@@ -22,7 +22,8 @@
|
|
|
22
22
|
"generate:version-ts": "tsx scripts/generate-version-ts.ts",
|
|
23
23
|
"build:bundle": "tsx scripts/build-bundle.ts",
|
|
24
24
|
"build:server": "mkdir -p bin && rm -rf bin/definitions bin/overrides && cp -r src/definitions bin/definitions && cp -r src/overrides bin/overrides && find bin/definitions -name '*.ts' -delete && tsc --project tsconfig.json",
|
|
25
|
-
"build": "pnpm extract && pnpm extract:icons && pnpm extract:tokens && pnpm generate:cdn-meta && pnpm generate:version-ts && pnpm build:bundle && pnpm build:server",
|
|
25
|
+
"build": "pnpm extract && pnpm extract:icons && pnpm extract:tokens && pnpm generate:cdn-meta && pnpm generate:version-ts && pnpm build:bundle && pnpm build:server && pnpm minify:compiled",
|
|
26
|
+
"minify:compiled": "tsx scripts/minify-compiled.ts",
|
|
26
27
|
"check-meta": "tsx scripts/check-meta-coherence.ts",
|
|
27
28
|
"test": "vitest run --config vitest.config.ts",
|
|
28
29
|
"test:coverage": "vitest run --coverage --config vitest.config.ts",
|
|
@@ -62,6 +63,8 @@
|
|
|
62
63
|
"devDependencies": {
|
|
63
64
|
"@ncds/ui-admin": "workspace:*",
|
|
64
65
|
"@types/lodash": "^4.17.24",
|
|
66
|
+
"glob": "^10.3.0",
|
|
67
|
+
"terser": "^5.28.0",
|
|
65
68
|
"@types/node": "^25.5.0",
|
|
66
69
|
"@types/react": "^18.3.20",
|
|
67
70
|
"esbuild": "^0.28.0",
|