@ncds/ui-admin-mcp 1.0.0-alpha.2 → 1.0.0-alpha.21
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/README.md +3 -3
- package/bin/components.bundle.js +18 -9
- 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 +2473 -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/listSidecarOverrides.d.ts +54 -0
- package/bin/tools/listSidecarOverrides.js +96 -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 +36 -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 +177 -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 +52 -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 +20 -3
- package/data/vertical-tab.json +220 -3
- package/package.json +28 -26
- package/templates/README.md +1 -1
- 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
|
@@ -1,3 +1,8 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* search_component tool — name/description/aliases 퍼지 키워드 검색 (순수 함수)
|
|
3
|
+
*
|
|
4
|
+
* Calculator: searchComponent — 입력(componentMap, query) → 출력(매칭 결과, 점수순 정렬)
|
|
5
|
+
*/
|
|
1
6
|
import type { ComponentData } from '../types.js';
|
|
2
7
|
import { type McpToolResponse } from '../utils/response.js';
|
|
3
8
|
/** search_component tool — name/description/aliases 퍼지 검색 후 점수순 반환 */
|
|
@@ -1,14 +1,14 @@
|
|
|
1
1
|
"use strict";
|
|
2
|
-
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
|
-
exports.searchComponent = void 0;
|
|
4
2
|
/**
|
|
5
3
|
* search_component tool — name/description/aliases 퍼지 키워드 검색 (순수 함수)
|
|
6
4
|
*
|
|
7
5
|
* Calculator: searchComponent — 입력(componentMap, query) → 출력(매칭 결과, 점수순 정렬)
|
|
8
6
|
*/
|
|
7
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
8
|
+
exports.searchComponent = void 0;
|
|
9
9
|
const dataLoader_js_1 = require("../utils/dataLoader.js");
|
|
10
|
-
const response_js_1 = require("../utils/response.js");
|
|
11
10
|
const fuzzyMatch_js_1 = require("../utils/fuzzyMatch.js");
|
|
11
|
+
const response_js_1 = require("../utils/response.js");
|
|
12
12
|
/** search_component tool — name/description/aliases 퍼지 검색 후 점수순 반환 */
|
|
13
13
|
const searchComponent = (componentMap, query) => {
|
|
14
14
|
if (!query.trim())
|
|
@@ -1,11 +1,8 @@
|
|
|
1
|
-
import type {
|
|
1
|
+
import type { ComplianceRulesData, ComponentData, TokenData } from '../types.js';
|
|
2
2
|
import type { CdnMeta } from '../utils/dataLoader.js';
|
|
3
3
|
import { type McpToolResponse } from '../utils/response.js';
|
|
4
|
-
export type { ValidationError } from '../utils/bemValidator.js';
|
|
5
|
-
export { buildRootClassMap } from '../utils/bemValidator.js';
|
|
6
|
-
export { buildTokenValueMap } from '../utils/tokenValidator.js';
|
|
7
4
|
/** validate_html tool 파라미터 */
|
|
8
|
-
|
|
5
|
+
interface ValidateHtmlParams {
|
|
9
6
|
componentMap: Map<string, ComponentData>;
|
|
10
7
|
rootClassMap: Map<string, string>;
|
|
11
8
|
html: string;
|
|
@@ -15,4 +12,9 @@ export interface ValidateHtmlParams {
|
|
|
15
12
|
tokenValueMap?: Map<string, string[]> | null;
|
|
16
13
|
}
|
|
17
14
|
/** validate_html tool — HTML의 NCUA BEM 클래스 정합성 + 디자인 시스템 준수도 검증 */
|
|
18
|
-
|
|
15
|
+
declare const validateHtml: (params: ValidateHtmlParams) => McpToolResponse;
|
|
16
|
+
export { validateHtml };
|
|
17
|
+
export type { ValidateHtmlParams };
|
|
18
|
+
export type { ValidationError } from '../utils/bemValidator.js';
|
|
19
|
+
export { buildRootClassMap } from '../utils/bemValidator.js';
|
|
20
|
+
export { buildTokenValueMap } from '../utils/tokenValidator.js';
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
"use strict";
|
|
2
2
|
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
|
-
exports.
|
|
3
|
+
exports.buildTokenValueMap = exports.buildRootClassMap = exports.validateHtml = void 0;
|
|
4
4
|
/**
|
|
5
5
|
* validate_html tool — NCUA HTML BEM 검증 + 디자인 시스템 준수도 검증 진입점
|
|
6
6
|
*
|
|
@@ -9,13 +9,9 @@ exports.validateHtml = exports.buildTokenValueMap = exports.buildRootClassMap =
|
|
|
9
9
|
* 이 파일은 오케스트레이션만 담당한다.
|
|
10
10
|
*/
|
|
11
11
|
const node_html_parser_1 = require("node-html-parser");
|
|
12
|
-
const response_js_1 = require("../utils/response.js");
|
|
13
12
|
const bemValidator_js_1 = require("../utils/bemValidator.js");
|
|
14
13
|
const compliance_js_1 = require("../utils/compliance.js");
|
|
15
|
-
|
|
16
|
-
Object.defineProperty(exports, "buildRootClassMap", { enumerable: true, get: function () { return bemValidator_js_2.buildRootClassMap; } });
|
|
17
|
-
var tokenValidator_js_1 = require("../utils/tokenValidator.js");
|
|
18
|
-
Object.defineProperty(exports, "buildTokenValueMap", { enumerable: true, get: function () { return tokenValidator_js_1.buildTokenValueMap; } });
|
|
14
|
+
const response_js_1 = require("../utils/response.js");
|
|
19
15
|
/** validate_html tool — HTML의 NCUA BEM 클래스 정합성 + 디자인 시스템 준수도 검증 */
|
|
20
16
|
const validateHtml = (params) => {
|
|
21
17
|
const { componentMap, rootClassMap, html, cdnMeta, tokenData, complianceRules, tokenValueMap } = params;
|
|
@@ -33,6 +29,9 @@ const validateHtml = (params) => {
|
|
|
33
29
|
return (0, response_js_1.successResponse)({ valid: true });
|
|
34
30
|
if (cdnMeta)
|
|
35
31
|
errors.push(...(0, bemValidator_js_1.checkCdnInclusion)(trimmed, cdnMeta));
|
|
32
|
+
// P13: Vertical Table context 검증 + 페이지 prefix required 마크 검출
|
|
33
|
+
errors.push(...collectVerticalTableContextErrors(root));
|
|
34
|
+
warnings.push(...collectCustomRequiredMarkWarnings(root));
|
|
36
35
|
const compliance = buildCompliance({
|
|
37
36
|
root,
|
|
38
37
|
componentMap,
|
|
@@ -66,6 +65,71 @@ const buildCompliance = (params) => {
|
|
|
66
65
|
customSeparation: customResult.customSeparation,
|
|
67
66
|
});
|
|
68
67
|
};
|
|
68
|
+
/** P13: Vertical Table context 위반 검출.
|
|
69
|
+
* 1) ncua-table--vertical 안 ncua-table__header-cell → horizontal 전용 오용.
|
|
70
|
+
* 2) ncua-table 안 <tr> 에 ncua-table__row 클래스 누락. */
|
|
71
|
+
const collectVerticalTableContextErrors = (root) => {
|
|
72
|
+
const errors = [];
|
|
73
|
+
// (1) vertical 안의 header-cell 검출
|
|
74
|
+
const verticalRoots = root.querySelectorAll('.ncua-table--vertical');
|
|
75
|
+
for (const v of verticalRoots) {
|
|
76
|
+
const headerCells = v.querySelectorAll('.ncua-table__header-cell');
|
|
77
|
+
for (const _ of headerCells) {
|
|
78
|
+
errors.push({
|
|
79
|
+
type: 'vertical_uses_horizontal_header_cell',
|
|
80
|
+
class: 'ncua-table__header-cell',
|
|
81
|
+
component: 'table',
|
|
82
|
+
message: "Vertical Table 안에서 'ncua-table__header-cell' 사용은 horizontal 전용 — vertical 라벨에는 <th scope='row' class='ncua-table__cell'> 사용 (Table.Cell with isHeader=true).",
|
|
83
|
+
suggestion: "render_to_html('table', { type: 'vertical', children: [...] }) 호출하여 정확한 BEM 출력을 받거나, <th class='ncua-table__header-cell'> 을 <th scope='row' class='ncua-table__cell'> 로 교체.",
|
|
84
|
+
});
|
|
85
|
+
}
|
|
86
|
+
}
|
|
87
|
+
// (2) ncua-table 안 <tr> 의 ncua-table__row 클래스 누락
|
|
88
|
+
const allTables = root.querySelectorAll('.ncua-table');
|
|
89
|
+
for (const t of allTables) {
|
|
90
|
+
const trs = t.querySelectorAll('tr');
|
|
91
|
+
for (const tr of trs) {
|
|
92
|
+
const classAttr = tr.getAttribute('class') ?? '';
|
|
93
|
+
if (!classAttr.split(/\s+/).includes('ncua-table__row')) {
|
|
94
|
+
errors.push({
|
|
95
|
+
type: 'missing_table_row_class',
|
|
96
|
+
class: 'ncua-table__row',
|
|
97
|
+
component: 'table',
|
|
98
|
+
message: "ncua-table 안의 <tr> 에 'ncua-table__row' 클래스 누락. NCDS Table 의 모든 row 는 이 클래스가 필요.",
|
|
99
|
+
suggestion: "render_to_html('table', ...) 출력을 그대로 사용. 수동 작성 시 모든 <tr> 에 class='ncua-table__row' 추가.",
|
|
100
|
+
});
|
|
101
|
+
// 같은 row 중복 보고 방지 — 첫 누락만
|
|
102
|
+
break;
|
|
103
|
+
}
|
|
104
|
+
}
|
|
105
|
+
}
|
|
106
|
+
return errors;
|
|
107
|
+
};
|
|
108
|
+
/** P13: 페이지 prefix 의 *-required 클래스로 필수 마크 자체 작성 검출.
|
|
109
|
+
* 텍스트가 정확히 `*` 한 글자이면 ncua-table__required 사용 권장. */
|
|
110
|
+
const collectCustomRequiredMarkWarnings = (root) => {
|
|
111
|
+
const warnings = [];
|
|
112
|
+
const allWithClass = root.querySelectorAll('[class]');
|
|
113
|
+
for (const el of allWithClass) {
|
|
114
|
+
const classAttr = el.getAttribute('class') ?? '';
|
|
115
|
+
const classes = classAttr.split(/\s+/).filter(Boolean);
|
|
116
|
+
// ncua- 가 아닌 prefix 의 *-required 또는 __required-mark 같은 형태
|
|
117
|
+
const matched = classes.find((c) => !c.startsWith('ncua-') && /(-required(?:-mark)?$|__required(?:-mark)?$)/.test(c));
|
|
118
|
+
if (!matched)
|
|
119
|
+
continue;
|
|
120
|
+
const text = (el.text ?? '').trim();
|
|
121
|
+
if (text !== '*')
|
|
122
|
+
continue;
|
|
123
|
+
warnings.push({
|
|
124
|
+
type: 'custom_required_mark',
|
|
125
|
+
class: matched,
|
|
126
|
+
component: 'table',
|
|
127
|
+
message: `페이지 prefix 클래스 '${matched}' 로 '*' 필수 마크를 자체 작성. NCDS 가 공식 element 'ncua-table__required' 를 제공.`,
|
|
128
|
+
suggestion: `<span class='${matched}'>*</span> 를 <span class='ncua-table__required'>*</span> 로 교체 (Vertical Table 라벨 셀 안에 prepend).`,
|
|
129
|
+
});
|
|
130
|
+
}
|
|
131
|
+
return warnings;
|
|
132
|
+
};
|
|
69
133
|
/** 최종 응답 빌드 */
|
|
70
134
|
const buildResponse = (params) => {
|
|
71
135
|
const { errors, warnings, invalidClassesSet, root, compliance } = params;
|
|
@@ -83,3 +147,7 @@ const buildResponse = (params) => {
|
|
|
83
147
|
...(compliance && { compliance }),
|
|
84
148
|
});
|
|
85
149
|
};
|
|
150
|
+
var bemValidator_js_2 = require("../utils/bemValidator.js");
|
|
151
|
+
Object.defineProperty(exports, "buildRootClassMap", { enumerable: true, get: function () { return bemValidator_js_2.buildRootClassMap; } });
|
|
152
|
+
var tokenValidator_js_1 = require("../utils/tokenValidator.js");
|
|
153
|
+
Object.defineProperty(exports, "buildTokenValueMap", { enumerable: true, get: function () { return tokenValidator_js_1.buildTokenValueMap; } });
|
package/bin/types.d.ts
CHANGED
|
@@ -7,11 +7,32 @@ export interface PropSpec {
|
|
|
7
7
|
default?: unknown;
|
|
8
8
|
values?: string[];
|
|
9
9
|
rawType?: string;
|
|
10
|
+
properties?: Record<string, PropSpec>;
|
|
11
|
+
/** 타입의 의미적 정체성 — 예: 'icon-component'는 SlotIconComponent 계열 아이콘 컴포넌트. renderer가 iconBundle resolve 대상 판정에 사용 */
|
|
12
|
+
semantic?: 'icon-component';
|
|
10
13
|
}
|
|
11
14
|
export interface ComponentUsage {
|
|
12
15
|
import: string;
|
|
13
16
|
react: Record<string, string>;
|
|
14
17
|
}
|
|
18
|
+
/** P8: allowedChildren 의 구조화 객체 형태 (P5-2).
|
|
19
|
+
* - required: 부모 안에 반드시 있어야 하는 자식
|
|
20
|
+
* - recommended: 강력히 권장 (없어도 통과)
|
|
21
|
+
* - optional: 선택적 (사용자 명시 요청 시만)
|
|
22
|
+
* - forbiddenInContext: { childKey: reason } — 이름은 valid 컴포넌트지만 이 부모 안에서는 사용 금지 + 대체 슬롯 안내 */
|
|
23
|
+
export interface AllowedChildrenSpec {
|
|
24
|
+
required?: string[];
|
|
25
|
+
recommended?: string[];
|
|
26
|
+
optional?: string[];
|
|
27
|
+
forbiddenInContext?: Record<string, string>;
|
|
28
|
+
}
|
|
29
|
+
/** P8: canonicalExamples 다중 시나리오 맵의 단일 항목 (P5-1). */
|
|
30
|
+
export interface CanonicalExampleEntry {
|
|
31
|
+
/** 시나리오 한 줄 설명. AI 가 어떤 시나리오를 선택할지 판단에 사용. */
|
|
32
|
+
description?: string;
|
|
33
|
+
/** render_to_html 입력으로 그대로 사용 가능한 props 트리. */
|
|
34
|
+
props: Record<string, unknown>;
|
|
35
|
+
}
|
|
15
36
|
export interface ComponentData {
|
|
16
37
|
name: string;
|
|
17
38
|
exportName: string;
|
|
@@ -26,7 +47,26 @@ export interface ComponentData {
|
|
|
26
47
|
seeAlso: string[];
|
|
27
48
|
/** Props가 확장하는 HTML 요소 — 에이전트가 표준 HTML 속성 사용 가능 여부를 판단 */
|
|
28
49
|
htmlElement?: string;
|
|
50
|
+
/** render_to_html에 전달할 실제 사용 예시 props — meta.ts에서 추출 */
|
|
51
|
+
usageExamples?: Record<string, Record<string, unknown>>;
|
|
29
52
|
props: Record<string, PropSpec>;
|
|
53
|
+
/** Compound 컴포넌트의 서브 컴포넌트 props 스펙 — 예: Modal.Header, Table.Row
|
|
54
|
+
* 키는 "Modal.Header" 형태의 dot notation. 서브가 없으면 undefined. */
|
|
55
|
+
subComponents?: Record<string, {
|
|
56
|
+
props: Record<string, PropSpec>;
|
|
57
|
+
}>;
|
|
58
|
+
/** P8: Composition Overrides (`src/overrides/composition.json`) 에서 병합되는 부모-자식 제약.
|
|
59
|
+
* 키는 컴포넌트 자체("DataGrid") 또는 서브("DataGrid.Table") PascalCase.
|
|
60
|
+
* 값은 단순 배열 (legacy) 또는 구조화 객체 (required/recommended/optional/forbiddenInContext) 모두 허용. */
|
|
61
|
+
allowedChildren?: Record<string, string[] | AllowedChildrenSpec>;
|
|
62
|
+
/** P8: composition overrides 에서 병합되는 허용 부모. */
|
|
63
|
+
allowedParents?: Record<string, string[]>;
|
|
64
|
+
/** P8: composition overrides 에서 병합되는 canonical 단일 시나리오 (legacy P1-6 호환). */
|
|
65
|
+
canonicalExample?: {
|
|
66
|
+
props: Record<string, unknown>;
|
|
67
|
+
};
|
|
68
|
+
/** P8: composition overrides 에서 병합되는 canonical 다중 시나리오 맵 (P5-1). 키는 시나리오 식별자. */
|
|
69
|
+
canonicalExamples?: Record<string, CanonicalExampleEntry>;
|
|
30
70
|
html?: Record<string, string>;
|
|
31
71
|
bemClasses: string[];
|
|
32
72
|
usage: ComponentUsage;
|
|
@@ -49,8 +89,9 @@ export interface ReactRuntime {
|
|
|
49
89
|
export interface Capability {
|
|
50
90
|
tool: string;
|
|
51
91
|
description: string;
|
|
92
|
+
available: boolean;
|
|
52
93
|
}
|
|
53
|
-
export type McpErrorCode = 'COMPONENT_NOT_FOUND' | 'EXPORT_NAME_MISSING' | 'COMPONENT_NOT_IN_BUNDLE' | 'RENDER_FAILED' | 'INVALID_TOKEN_CATEGORY';
|
|
94
|
+
export type McpErrorCode = 'COMPONENT_NOT_FOUND' | 'EXPORT_NAME_MISSING' | 'COMPONENT_NOT_IN_BUNDLE' | 'RENDER_FAILED' | 'INVALID_TOKEN_CATEGORY' | 'ICON_NOT_FOUND' | 'INVALID_PARAMS' | 'INTERNAL_ERROR';
|
|
54
95
|
export interface TokenEntry {
|
|
55
96
|
name: string;
|
|
56
97
|
value: string;
|
|
@@ -67,6 +108,24 @@ export interface TokenData {
|
|
|
67
108
|
categories: string[];
|
|
68
109
|
groups: TokenGroup[];
|
|
69
110
|
}
|
|
111
|
+
export interface JsApiInfo {
|
|
112
|
+
className: string;
|
|
113
|
+
constructor: string;
|
|
114
|
+
constructorParams: Record<string, string>;
|
|
115
|
+
methods: string[];
|
|
116
|
+
staticMethods?: string[];
|
|
117
|
+
example: string;
|
|
118
|
+
/** Story 5.9: CDN init script 생성 시 사용자 props 와 deep merge 되는 default 옵션. JSON-serializable 만 (함수 prop 없음). */
|
|
119
|
+
cdnDefaults?: Record<string, unknown>;
|
|
120
|
+
/**
|
|
121
|
+
* CDN 호출 패턴.
|
|
122
|
+
* - A: `new ncua.X(element, options)` — element가 곧 컨테이너
|
|
123
|
+
* - B: `new ncua.X(options)` — options.container로 자동 append
|
|
124
|
+
* - C: `new ncua.X(options)` — getElement() + 수동 append
|
|
125
|
+
* 부재 시 React 정적 HTML 경로로 fallback.
|
|
126
|
+
*/
|
|
127
|
+
cdnPattern?: 'A' | 'B' | 'C';
|
|
128
|
+
}
|
|
70
129
|
export interface ComplianceRuleHint {
|
|
71
130
|
attr: string;
|
|
72
131
|
values: string[];
|
|
@@ -6,22 +6,28 @@
|
|
|
6
6
|
import type { parse } from 'node-html-parser';
|
|
7
7
|
import type { ComponentData } from '../types.js';
|
|
8
8
|
import type { CdnMeta } from './dataLoader.js';
|
|
9
|
-
|
|
10
|
-
type: 'invalid_modifier' | 'invalid_element' | 'unknown_component' | 'missing_root_class' | 'mixed_namespace' | 'missing_cdn'
|
|
9
|
+
interface ValidationError {
|
|
10
|
+
type: 'invalid_modifier' | 'invalid_element' | 'unknown_component' | 'missing_root_class' | 'mixed_namespace' | 'missing_cdn'
|
|
11
|
+
/** P13: Vertical Table 안에서 horizontal 전용 `ncua-table__header-cell` 사용 */
|
|
12
|
+
| 'vertical_uses_horizontal_header_cell'
|
|
13
|
+
/** P13: Vertical/Horizontal Table 안 `<tr>` 에 `ncua-table__row` 클래스 누락 */
|
|
14
|
+
| 'missing_table_row_class'
|
|
15
|
+
/** P13: 페이지 prefix `*-required` 클래스로 필수 마크 자체 작성 (ncua-table__required 사용 권장) */
|
|
16
|
+
| 'custom_required_mark';
|
|
11
17
|
class: string;
|
|
12
18
|
component: string;
|
|
13
19
|
message: string;
|
|
14
20
|
suggestion: string;
|
|
15
21
|
}
|
|
16
22
|
/** 엘리먼트에서 class 목록을 파싱 */
|
|
17
|
-
|
|
23
|
+
declare const getClassList: (el: {
|
|
18
24
|
getAttribute: (name: string) => string | undefined;
|
|
19
25
|
}) => string[];
|
|
20
26
|
/** root class → component name 매핑 빌드 — server.ts에서 1회 호출 */
|
|
21
|
-
|
|
27
|
+
declare const buildRootClassMap: (componentMap: Map<string, ComponentData>) => Map<string, string>;
|
|
22
28
|
/** 엘리먼트별 BEM 클래스 검증 → errors + warnings + invalidClasses 수집 */
|
|
23
|
-
|
|
24
|
-
elements: ReturnType<ReturnType<typeof parse>[
|
|
29
|
+
declare const collectBemErrors: (params: {
|
|
30
|
+
elements: ReturnType<ReturnType<typeof parse>["querySelectorAll"]>;
|
|
25
31
|
rootClassMap: Map<string, string>;
|
|
26
32
|
componentMap: Map<string, ComponentData>;
|
|
27
33
|
}) => {
|
|
@@ -31,6 +37,8 @@ export declare const collectBemErrors: (params: {
|
|
|
31
37
|
hasNcuaClasses: boolean;
|
|
32
38
|
};
|
|
33
39
|
/** 잘못된 클래스를 제거하여 fixed_html 생성 */
|
|
34
|
-
|
|
40
|
+
declare const buildFixedHtml: (root: ReturnType<typeof parse>, invalidClassesSet: Set<string>) => string;
|
|
35
41
|
/** CDN CSS/JS 링크 포함 여부 검증 */
|
|
36
|
-
|
|
42
|
+
declare const checkCdnInclusion: (html: string, cdnMeta: CdnMeta) => ValidationError[];
|
|
43
|
+
export { buildFixedHtml, buildRootClassMap, checkCdnInclusion, collectBemErrors, getClassList };
|
|
44
|
+
export type { ValidationError };
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
"use strict";
|
|
2
2
|
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
|
-
exports.
|
|
3
|
+
exports.getClassList = exports.collectBemErrors = exports.checkCdnInclusion = exports.buildRootClassMap = exports.buildFixedHtml = void 0;
|
|
4
4
|
// ── 상수 ─────────────────────────────────────────────────────────────────
|
|
5
5
|
const MAX_SIMILAR_SUGGESTIONS = 3;
|
|
6
6
|
// ── 순수 헬퍼 ────────────────────────────────────────────────────────────
|
|
@@ -102,7 +102,7 @@ const collectBemErrors = (params) => {
|
|
|
102
102
|
const validSetCache = new Map();
|
|
103
103
|
let foundNcua = false;
|
|
104
104
|
for (const el of elements) {
|
|
105
|
-
const classes =
|
|
105
|
+
const classes = getClassList(el);
|
|
106
106
|
const ncuaClasses = classes.filter((c) => c.startsWith('ncua-'));
|
|
107
107
|
if (ncuaClasses.length === 0)
|
|
108
108
|
continue;
|
|
@@ -119,11 +119,20 @@ const collectBemErrors = (params) => {
|
|
|
119
119
|
reportUnknownClasses(ncuaClasses, errors, invalidClassesSet);
|
|
120
120
|
continue;
|
|
121
121
|
}
|
|
122
|
+
// biome-ignore lint/style/noNonNullAssertion: 직전 가드(rootClassMap.has)로 존재 보장
|
|
122
123
|
const componentName = rootClassMap.get(rootClass);
|
|
123
124
|
const component = componentMap.get(componentName);
|
|
125
|
+
if (!component) {
|
|
126
|
+
// rootClassMap 에는 있는데 componentMap 에 없는 비정상 상태 (호출자가 두 Map 을 분리 빌드한 경우).
|
|
127
|
+
// graceful skip — rootClass 가 알 수 없는 컴포넌트인 경로와 동일 처리.
|
|
128
|
+
reportUnknownClasses(ncuaClasses, errors, invalidClassesSet);
|
|
129
|
+
continue;
|
|
130
|
+
}
|
|
124
131
|
if (!validSetCache.has(componentName))
|
|
125
132
|
validSetCache.set(componentName, new Set(component.bemClasses));
|
|
126
|
-
validateBemClasses(ncuaClasses,
|
|
133
|
+
validateBemClasses(ncuaClasses,
|
|
134
|
+
// biome-ignore lint/style/noNonNullAssertion: 직전 has/set 보장
|
|
135
|
+
validSetCache.get(componentName), componentName, component.bemClasses, errors, invalidClassesSet);
|
|
127
136
|
}
|
|
128
137
|
return { errors, warnings, invalidClassesSet, hasNcuaClasses: foundNcua };
|
|
129
138
|
};
|
|
@@ -133,6 +142,7 @@ const collectMixedNamespaceWarning = (classes, ncuaClasses, rootClass, rootClass
|
|
|
133
142
|
const customClasses = classes.filter((c) => !c.startsWith('ncua-'));
|
|
134
143
|
if (customClasses.length === 0)
|
|
135
144
|
return;
|
|
145
|
+
// biome-ignore lint/style/noNonNullAssertion: 삼항 가드로 rootClassMap.has(rootClass) 보장
|
|
136
146
|
const compName = rootClass && rootClassMap.has(rootClass) ? rootClassMap.get(rootClass) : 'unknown';
|
|
137
147
|
warnings.push({
|
|
138
148
|
type: 'mixed_namespace',
|
|
@@ -151,7 +161,9 @@ const collectMissingRootClassError = (ncuaClasses, rootClass, rootClassMap, erro
|
|
|
151
161
|
return;
|
|
152
162
|
errors.push({
|
|
153
163
|
type: 'missing_root_class',
|
|
164
|
+
// biome-ignore lint/style/noNonNullAssertion: isModifierWithoutRoot 가드에 rootClass != null 포함
|
|
154
165
|
class: rootClass,
|
|
166
|
+
// biome-ignore lint/style/noNonNullAssertion: 위와 동일 가드 (rootClass != null + has)
|
|
155
167
|
component: rootClassMap.get(rootClass),
|
|
156
168
|
message: `Root class '${rootClass}' is missing. Modifiers/elements require their root class.`,
|
|
157
169
|
suggestion: `Add '${rootClass}' class to the same element.`,
|
|
@@ -160,7 +172,7 @@ const collectMissingRootClassError = (ncuaClasses, rootClass, rootClassMap, erro
|
|
|
160
172
|
/** 잘못된 클래스를 제거하여 fixed_html 생성 */
|
|
161
173
|
const buildFixedHtml = (root, invalidClassesSet) => {
|
|
162
174
|
for (const el of root.querySelectorAll('[class]')) {
|
|
163
|
-
const classes =
|
|
175
|
+
const classes = getClassList(el);
|
|
164
176
|
const filtered = classes.filter((c) => !invalidClassesSet.has(c));
|
|
165
177
|
if (filtered.length > 0) {
|
|
166
178
|
el.setAttribute('class', filtered.join(' '));
|
|
@@ -3,12 +3,11 @@
|
|
|
3
3
|
*
|
|
4
4
|
* 토큰 검증은 tokenValidator.ts에 분리.
|
|
5
5
|
*/
|
|
6
|
-
import { parse } from 'node-html-parser';
|
|
7
|
-
import type {
|
|
6
|
+
import type { parse } from 'node-html-parser';
|
|
7
|
+
import type { ComplianceError, ComplianceRulesData, ComplianceSummary, ComponentData } from '../types.js';
|
|
8
8
|
import type { ValidationError } from './bemValidator.js';
|
|
9
|
-
export { detectTokenIssues } from './tokenValidator.js';
|
|
10
9
|
/** NCUA 컴포넌트 누락 감지 — 태그 인덱스로 O(n) 매칭 */
|
|
11
|
-
|
|
10
|
+
declare const detectNcuaOmission: (params: {
|
|
12
11
|
root: ReturnType<typeof parse>;
|
|
13
12
|
complianceRules: ComplianceRulesData;
|
|
14
13
|
componentMap: Map<string, ComponentData>;
|
|
@@ -20,7 +19,7 @@ export declare const detectNcuaOmission: (params: {
|
|
|
20
19
|
};
|
|
21
20
|
};
|
|
22
21
|
/** NCUA 컴포넌트 내부 커스텀 스타일 감지 */
|
|
23
|
-
|
|
22
|
+
declare const detectCustomNotSeparated: (params: {
|
|
24
23
|
root: ReturnType<typeof parse>;
|
|
25
24
|
rootClassMap: Map<string, string>;
|
|
26
25
|
warnings: ValidationError[];
|
|
@@ -32,7 +31,7 @@ export declare const detectCustomNotSeparated: (params: {
|
|
|
32
31
|
};
|
|
33
32
|
};
|
|
34
33
|
/** 준수도 점수 + 요약 빌드 */
|
|
35
|
-
|
|
34
|
+
declare const buildComplianceSummary: (params: {
|
|
36
35
|
ncuaErrors: ComplianceError[];
|
|
37
36
|
tokenErrors: ComplianceError[];
|
|
38
37
|
customErrors: ComplianceError[];
|
|
@@ -50,3 +49,5 @@ export declare const buildComplianceSummary: (params: {
|
|
|
50
49
|
violated: number;
|
|
51
50
|
};
|
|
52
51
|
}) => ComplianceSummary;
|
|
52
|
+
export { buildComplianceSummary, detectCustomNotSeparated, detectNcuaOmission };
|
|
53
|
+
export { detectTokenIssues } from './tokenValidator.js';
|
package/bin/utils/compliance.js
CHANGED
|
@@ -1,9 +1,7 @@
|
|
|
1
1
|
"use strict";
|
|
2
2
|
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
|
-
exports.
|
|
3
|
+
exports.detectTokenIssues = exports.detectNcuaOmission = exports.detectCustomNotSeparated = exports.buildComplianceSummary = void 0;
|
|
4
4
|
const bemValidator_js_1 = require("./bemValidator.js");
|
|
5
|
-
var tokenValidator_js_1 = require("./tokenValidator.js");
|
|
6
|
-
Object.defineProperty(exports, "detectTokenIssues", { enumerable: true, get: function () { return tokenValidator_js_1.detectTokenIssues; } });
|
|
7
5
|
// ── 상수 ─────────────────────────────────────────────────────────────────
|
|
8
6
|
const MAX_COMPLIANCE_ERRORS = 10;
|
|
9
7
|
const NCUA_NOT_USED_MEDIUM_WEIGHT = 0.5;
|
|
@@ -183,11 +181,15 @@ const buildComplianceSummary = (params) => {
|
|
|
183
181
|
const isMedium = err.type === 'ncua_not_used' && err.severity === 'warning';
|
|
184
182
|
weightedViolations += isMedium ? NCUA_NOT_USED_MEDIUM_WEIGHT : COMPLIANCE_WEIGHTS[err.type];
|
|
185
183
|
}
|
|
186
|
-
const
|
|
184
|
+
const ncuaNotUsedCount = ncuaErrors.length;
|
|
185
|
+
const totalCheckpoints = ncuaUsage.used +
|
|
186
|
+
ncuaNotUsedCount +
|
|
187
187
|
(tokenUsage.correct + tokenUsage.missing + tokenUsage.invalid) +
|
|
188
188
|
(customSeparation.clean + customSeparation.violated);
|
|
189
|
+
// biome-ignore lint/style/noMagicNumbers: 점수 0~1 boundary (clamp)
|
|
189
190
|
const score = totalCheckpoints === 0 ? 1.0 : Math.max(0, Math.min(1, 1 - weightedViolations / totalCheckpoints));
|
|
190
191
|
return {
|
|
192
|
+
// biome-ignore lint/style/noMagicNumbers: 2자리 소수점 반올림 (×100/100)
|
|
191
193
|
score: Math.round(score * 100) / 100,
|
|
192
194
|
errors: allErrors.slice(0, MAX_COMPLIANCE_ERRORS),
|
|
193
195
|
ncuaUsage,
|
|
@@ -197,3 +199,5 @@ const buildComplianceSummary = (params) => {
|
|
|
197
199
|
};
|
|
198
200
|
};
|
|
199
201
|
exports.buildComplianceSummary = buildComplianceSummary;
|
|
202
|
+
var tokenValidator_js_1 = require("./tokenValidator.js");
|
|
203
|
+
Object.defineProperty(exports, "detectTokenIssues", { enumerable: true, get: function () { return tokenValidator_js_1.detectTokenIssues; } });
|
|
@@ -1,33 +1,62 @@
|
|
|
1
|
-
import type { ComponentData, IconData,
|
|
2
|
-
|
|
1
|
+
import type { ComplianceRulesData, ComponentData, IconData, JsApiInfo, TokenData } from '../types.js';
|
|
2
|
+
declare const DEFAULT_DATA_DIR: string;
|
|
3
|
+
interface CompositionEntry {
|
|
4
|
+
bemClassesExtra?: string[];
|
|
5
|
+
/** P5-2: 단순 배열 (legacy) 또는 구조화 객체 모두 허용 */
|
|
6
|
+
allowedChildren?: Record<string, string[] | import('../types.js').AllowedChildrenSpec>;
|
|
7
|
+
allowedParents?: Record<string, string[]>;
|
|
8
|
+
/** P1-6 legacy 단일 시나리오 */
|
|
9
|
+
canonicalExample?: {
|
|
10
|
+
props: Record<string, unknown>;
|
|
11
|
+
};
|
|
12
|
+
/** P5-1 다중 시나리오 맵 */
|
|
13
|
+
canonicalExamples?: Record<string, import('../types.js').CanonicalExampleEntry>;
|
|
14
|
+
/** P13: 컴포넌트 description 끝에 append (메타 보강). list_components / search_component 응답 개선용. */
|
|
15
|
+
descriptionExtra?: string;
|
|
16
|
+
/** P13: 컴포넌트 aliases 와 union (중복 제거). search_component 별칭 보강. */
|
|
17
|
+
aliasesExtra?: string[];
|
|
18
|
+
/**
|
|
19
|
+
* coverageScore 분모에서 제외할 보강 영역 목록 — 구조상 채울 수 없는 영역을 선언.
|
|
20
|
+
* 예: extract 가 BEM 클래스를 완전히 잡는 컴포넌트는 ["bemClassesExtra"] 선언 → 분모 0.10 차감 → 달성 가능한 나머지 영역만 채우면 1.0 달성.
|
|
21
|
+
* compound/non-compound 구조적 제외(allowedChildren)와 union 되어 최종 notApplicable 결정.
|
|
22
|
+
*/
|
|
23
|
+
notApplicableAreas?: string[];
|
|
24
|
+
}
|
|
25
|
+
declare const loadCompositionOverrides: () => Record<string, CompositionEntry>;
|
|
3
26
|
export interface CdnMeta {
|
|
4
27
|
version: string;
|
|
5
28
|
css: string;
|
|
6
29
|
js: string;
|
|
7
30
|
}
|
|
8
|
-
|
|
31
|
+
interface IconMeta {
|
|
9
32
|
packageName: string;
|
|
10
33
|
version: string;
|
|
11
|
-
cdn: string;
|
|
12
34
|
}
|
|
13
|
-
/** data/ 디렉토리의 JSON 파일을 읽어 componentMap
|
|
14
|
-
|
|
35
|
+
/** data/ 디렉토리의 JSON 파일을 읽어 componentMap + compositionOverrides로 반환 */
|
|
36
|
+
declare const loadComponentsFromDir: (dataDir: string) => {
|
|
37
|
+
map: Map<string, ComponentData>;
|
|
38
|
+
compositionOverrides: Record<string, CompositionEntry>;
|
|
39
|
+
};
|
|
15
40
|
/** data/_icons.json을 읽어 IconData로 반환 (파일 없으면 빈 데이터) */
|
|
16
|
-
|
|
41
|
+
declare const loadIconData: (dataDir: string) => IconData;
|
|
17
42
|
/** _meta.json 파싱 결과 */
|
|
18
|
-
|
|
43
|
+
interface MetaData {
|
|
19
44
|
cdn: CdnMeta | null;
|
|
20
45
|
icon: IconMeta | null;
|
|
21
46
|
}
|
|
22
47
|
/** data/_meta.json에서 CDN + icon 메타를 1회 읽기로 로드 */
|
|
23
|
-
|
|
48
|
+
declare const loadMeta: (dataDir: string) => MetaData;
|
|
24
49
|
/** data/_tokens.json을 읽어 TokenData로 반환 (파일 없으면 빈 데이터) */
|
|
25
|
-
|
|
50
|
+
declare const loadTokenData: (dataDir: string) => TokenData;
|
|
26
51
|
/** definitions/compliance-rules.json을 로드하여 ComplianceRulesData로 반환 (파일 없으면 null) */
|
|
27
|
-
|
|
52
|
+
declare const loadComplianceRules: (definitionsDir: string) => ComplianceRulesData | null;
|
|
28
53
|
/** definitions/instructions.md를 로드하여 문자열 반환 */
|
|
29
|
-
|
|
54
|
+
declare const loadInstructions: (definitionsDir: string) => string;
|
|
55
|
+
/** definitions/js-api.json을 로드하여 exportName→JsApiInfo Map 반환 */
|
|
56
|
+
declare const loadJsApi: (definitionsDir: string) => Map<string, JsApiInfo>;
|
|
30
57
|
/** componentMap에서 이름으로 단일 컴포넌트 조회 */
|
|
31
|
-
|
|
58
|
+
declare const getComponent: (map: Map<string, ComponentData>, name: string) => ComponentData | undefined;
|
|
32
59
|
/** componentMap의 모든 컴포넌트를 배열로 반환 */
|
|
33
|
-
|
|
60
|
+
declare const getAllComponents: (map: Map<string, ComponentData>) => ComponentData[];
|
|
61
|
+
export { DEFAULT_DATA_DIR, getAllComponents, getComponent, loadComplianceRules, loadCompositionOverrides, loadComponentsFromDir, loadIconData, loadInstructions, loadJsApi, loadMeta, loadTokenData, };
|
|
62
|
+
export type { CompositionEntry, IconMeta, MetaData };
|