@monoharada/wcf-mcp 0.9.1 → 0.10.0

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/core/tokens.mjs CHANGED
@@ -9,6 +9,19 @@ const TOKEN_MISUSE_ALLOWED_TYPES = Object.freeze(new Set(['color', 'spacing']));
9
9
  const TOKEN_THEMES = Object.freeze(new Set(['light', 'dark', 'all']));
10
10
  const WCAG_LEVELS = Object.freeze(new Set(['A', 'AA', 'AAA', 'all']));
11
11
 
12
+ function getThemeConfig(designTokensData) {
13
+ const available = Array.isArray(designTokensData?.themes?.available) && designTokensData.themes.available.length > 0
14
+ ? [...new Set(designTokensData.themes.available.map((theme) => String(theme).trim().toLowerCase()).filter(Boolean))]
15
+ : ['light'];
16
+ const defaultTheme = typeof designTokensData?.themes?.default === 'string' && available.includes(String(designTokensData.themes.default).toLowerCase())
17
+ ? String(designTokensData.themes.default).toLowerCase()
18
+ : available[0];
19
+ return {
20
+ defaultTheme,
21
+ available,
22
+ };
23
+ }
24
+
12
25
  export function normalizeTokenValue(value) {
13
26
  if (typeof value === 'string') return value.trim().toLowerCase().replace(/\s+/g, ' ');
14
27
  if (typeof value === 'number') return String(value);
@@ -54,7 +67,7 @@ export function normalizeTokenIdentifier(value) {
54
67
  return `--${raw.replace(/^[-]+/, '')}`;
55
68
  }
56
69
 
57
- export function resolveTokenTheme(theme) {
70
+ export function resolveTokenTheme(theme, designTokensData) {
58
71
  const requested = String(theme ?? 'light').trim().toLowerCase() || 'light';
59
72
  if (!TOKEN_THEMES.has(requested)) {
60
73
  return {
@@ -63,29 +76,46 @@ export function resolveTokenTheme(theme) {
63
76
  message: `Unsupported theme: ${requested}. Allowed values are light, dark, all.`,
64
77
  };
65
78
  }
66
- if (requested !== 'light') {
79
+
80
+ const themeConfig = getThemeConfig(designTokensData);
81
+ if (requested === 'all') {
82
+ return {
83
+ ok: true,
84
+ requested,
85
+ resolved: themeConfig.defaultTheme,
86
+ available: themeConfig.available,
87
+ mode: 'all',
88
+ };
89
+ }
90
+
91
+ if (!themeConfig.available.includes(requested)) {
67
92
  return {
68
93
  ok: false,
69
94
  errorCode: 'INVALID_THEME',
70
- message: `Theme "${requested}" is not available yet. Use theme="light" (NG-06).`,
95
+ message: `Theme "${requested}" is not supported. Available themes in current data: ${themeConfig.available.join(', ')}.`,
71
96
  };
72
97
  }
98
+
73
99
  return {
74
100
  ok: true,
75
101
  requested,
76
- resolved: 'light',
77
- available: ['light'],
102
+ resolved: requested,
103
+ available: themeConfig.available,
104
+ mode: 'single',
78
105
  };
79
106
  }
80
107
 
81
108
  export function extractReferencedTokenNames(value) {
82
109
  if (typeof value !== 'string') return [];
83
110
  const refs = [];
84
- const re = /var\(\s*(--[^,\s)]+)\s*(?:,\s*[^)]+)?\)/g;
111
+ const re = /var\(([^)]*)\)/g;
85
112
  let match;
86
113
  while ((match = re.exec(value))) {
87
- const tokenName = normalizeTokenIdentifier(match[1]);
88
- if (tokenName) refs.push(tokenName);
114
+ const candidates = String(match[1] ?? '').match(/--[^,\s)]+/g) ?? [];
115
+ for (const candidate of candidates) {
116
+ const tokenName = normalizeTokenIdentifier(candidate);
117
+ if (tokenName) refs.push(tokenName);
118
+ }
89
119
  }
90
120
  return [...new Set(refs)];
91
121
  }
@@ -141,12 +171,10 @@ export function buildTokenRelationshipIndex(designTokensData) {
141
171
  /**
142
172
  * Extract which components reference which design tokens via var() in CEM cssProperties.
143
173
  * Returns Map<tokenName, Set<componentTagName>>.
144
- * DD-25: var(--token, fallback) fallback values are not extracted (known limitation).
145
174
  */
146
175
  export function buildComponentTokenReferencedBy(manifest) {
147
176
  const result = new Map();
148
177
  const modules = Array.isArray(manifest?.modules) ? manifest.modules : [];
149
- const varRe = /var\((--[\w-]+)/g;
150
178
  for (const mod of modules) {
151
179
  const declarations = Array.isArray(mod?.declarations) ? mod.declarations : [];
152
180
  for (const decl of declarations) {
@@ -155,9 +183,8 @@ export function buildComponentTokenReferencedBy(manifest) {
155
183
  const cssProps = Array.isArray(decl?.cssProperties) ? decl.cssProperties : [];
156
184
  for (const prop of cssProps) {
157
185
  const defaultVal = typeof prop?.default === 'string' ? prop.default : '';
158
- let m;
159
- while ((m = varRe.exec(defaultVal)) !== null) {
160
- const tokenName = normalizeTokenIdentifier(m[1]);
186
+ for (const tokenRef of extractReferencedTokenNames(defaultVal)) {
187
+ const tokenName = normalizeTokenIdentifier(tokenRef);
161
188
  if (!tokenName) continue;
162
189
  if (!result.has(tokenName)) result.set(tokenName, new Set());
163
190
  result.get(tokenName).add(tag);
@@ -235,6 +262,30 @@ function buildUsageExamples(token) {
235
262
  return [`.example { --token-value: ${cssVar}; }`];
236
263
  }
237
264
 
265
+ function resolveTokenValueForTheme(token, themeInfo) {
266
+ if (token?.themeValues && typeof token.themeValues === 'object') {
267
+ const themedValue = token.themeValues[themeInfo.resolved];
268
+ if (typeof themedValue === 'string' && themedValue.trim() !== '') {
269
+ return themedValue;
270
+ }
271
+ }
272
+ return token?.value;
273
+ }
274
+
275
+ function serializeTokenForTheme(token, themeInfo, { includeThemeValues = false } = {}) {
276
+ const serialized = {
277
+ ...toTokenSummary({
278
+ ...token,
279
+ value: resolveTokenValueForTheme(token, themeInfo),
280
+ }),
281
+ };
282
+ if (token?.group !== undefined) serialized.group = token.group;
283
+ if (includeThemeValues && token?.themeValues && typeof token.themeValues === 'object') {
284
+ serialized.themeValues = token.themeValues;
285
+ }
286
+ return serialized;
287
+ }
288
+
238
289
  function buildTokenErrorPayload(code, message, extra = {}) {
239
290
  return {
240
291
  isError: true,
@@ -253,7 +304,7 @@ export function buildDesignTokenDetailPayload(designTokensData, name, theme) {
253
304
  );
254
305
  }
255
306
 
256
- const themeInfo = resolveTokenTheme(theme);
307
+ const themeInfo = resolveTokenTheme(theme, designTokensData);
257
308
  if (!themeInfo.ok) {
258
309
  return buildTokenErrorPayload(themeInfo.errorCode, themeInfo.message);
259
310
  }
@@ -294,7 +345,7 @@ export function buildDesignTokenDetailPayload(designTokensData, name, theme) {
294
345
  isError: false,
295
346
  payload: {
296
347
  token: {
297
- ...toTokenSummary(token),
348
+ ...serializeTokenForTheme(token, themeInfo, { includeThemeValues: true }),
298
349
  group: token?.group ?? null,
299
350
  },
300
351
  references,
@@ -318,7 +369,7 @@ export function buildDesignTokensPayload(designTokensData, { type, category, que
318
369
  );
319
370
  }
320
371
 
321
- const themeInfo = resolveTokenTheme(theme);
372
+ const themeInfo = resolveTokenTheme(theme, designTokensData);
322
373
  if (!themeInfo.ok) {
323
374
  return buildTokenErrorPayload(themeInfo.errorCode, themeInfo.message);
324
375
  }
@@ -335,7 +386,9 @@ export function buildDesignTokensPayload(designTokensData, { type, category, que
335
386
  isError: false,
336
387
  payload: {
337
388
  total: tokens.length,
338
- tokens,
389
+ tokens: tokens.map((token) =>
390
+ serializeTokenForTheme(token, themeInfo, { includeThemeValues: themeInfo.requested === 'all' })
391
+ ),
339
392
  summary: designTokensData.summary,
340
393
  theme: {
341
394
  requested: themeInfo.requested,
package/core.mjs CHANGED
@@ -8,6 +8,7 @@
8
8
  * Actual logic lives in core/ sub-modules (DD-02, DD-09).
9
9
  */
10
10
 
11
+ import fs from 'node:fs/promises';
11
12
  import { McpServer } from '@modelcontextprotocol/sdk/server/mcp.js';
12
13
  import { registerAll } from './core/register.mjs';
13
14
 
@@ -118,6 +119,9 @@ export async function createMcpServer(loadJsonData, loadValidator, options = {})
118
119
  const loadTextData = typeof options?.loadTextData === 'function'
119
120
  ? options.loadTextData
120
121
  : null;
122
+ const loadTextDataFromPath = typeof options?.loadTextDataFromPath === 'function'
123
+ ? options.loadTextDataFromPath
124
+ : null;
121
125
 
122
126
  const loadJson = async (fileName) => {
123
127
  const override = pluginDataSourceMap.get(fileName);
@@ -128,6 +132,13 @@ export async function createMcpServer(loadJsonData, loadValidator, options = {})
128
132
  return loadJsonDataFromPath(override.path, fileName, override.pluginName);
129
133
  };
130
134
  const loadText = async (fileName) => {
135
+ const override = pluginDataSourceMap.get(fileName);
136
+ if (override) {
137
+ if (loadTextDataFromPath) {
138
+ return loadTextDataFromPath(override.path, fileName, override.pluginName);
139
+ }
140
+ return fs.readFile(override.path, 'utf8');
141
+ }
131
142
  if (!loadTextData) throw new Error(`Text data loader not configured for ${fileName}`);
132
143
  return loadTextData(fileName);
133
144
  };
@@ -145,6 +156,7 @@ export async function createMcpServer(loadJsonData, loadValidator, options = {})
145
156
  buildSlotNameMap = () => new Map(),
146
157
  detectInvalidSlotName = () => [],
147
158
  detectMissingRequiredAttributes = () => [],
159
+ detectDuplicateIdsInMarkup = () => [],
148
160
  detectOrphanedChildComponents = () => [],
149
161
  detectEmptyInteractiveElement = () => [],
150
162
  detectNonLowercaseAttributes = () => [],
@@ -226,6 +238,7 @@ export async function createMcpServer(loadJsonData, loadValidator, options = {})
226
238
  buildSlotNameMap,
227
239
  detectInvalidSlotName,
228
240
  detectMissingRequiredAttributes,
241
+ detectDuplicateIdsInMarkup,
229
242
  detectOrphanedChildComponents,
230
243
  detectEmptyInteractiveElement,
231
244
  detectNonLowercaseAttributes,