@mp3wizard/figma-console-mcp 1.14.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/LICENSE +21 -0
- package/README.md +816 -0
- package/dist/apps/design-system-dashboard/scoring/accessibility.d.ts +14 -0
- package/dist/apps/design-system-dashboard/scoring/accessibility.d.ts.map +1 -0
- package/dist/apps/design-system-dashboard/scoring/accessibility.js +278 -0
- package/dist/apps/design-system-dashboard/scoring/accessibility.js.map +1 -0
- package/dist/apps/design-system-dashboard/scoring/component-metadata.d.ts +29 -0
- package/dist/apps/design-system-dashboard/scoring/component-metadata.d.ts.map +1 -0
- package/dist/apps/design-system-dashboard/scoring/component-metadata.js +358 -0
- package/dist/apps/design-system-dashboard/scoring/component-metadata.js.map +1 -0
- package/dist/apps/design-system-dashboard/scoring/consistency.d.ts +14 -0
- package/dist/apps/design-system-dashboard/scoring/consistency.d.ts.map +1 -0
- package/dist/apps/design-system-dashboard/scoring/consistency.js +342 -0
- package/dist/apps/design-system-dashboard/scoring/consistency.js.map +1 -0
- package/dist/apps/design-system-dashboard/scoring/coverage.d.ts +14 -0
- package/dist/apps/design-system-dashboard/scoring/coverage.d.ts.map +1 -0
- package/dist/apps/design-system-dashboard/scoring/coverage.js +231 -0
- package/dist/apps/design-system-dashboard/scoring/coverage.js.map +1 -0
- package/dist/apps/design-system-dashboard/scoring/engine.d.ts +27 -0
- package/dist/apps/design-system-dashboard/scoring/engine.d.ts.map +1 -0
- package/dist/apps/design-system-dashboard/scoring/engine.js +93 -0
- package/dist/apps/design-system-dashboard/scoring/engine.js.map +1 -0
- package/dist/apps/design-system-dashboard/scoring/naming-semantics.d.ts +14 -0
- package/dist/apps/design-system-dashboard/scoring/naming-semantics.d.ts.map +1 -0
- package/dist/apps/design-system-dashboard/scoring/naming-semantics.js +309 -0
- package/dist/apps/design-system-dashboard/scoring/naming-semantics.js.map +1 -0
- package/dist/apps/design-system-dashboard/scoring/token-architecture.d.ts +14 -0
- package/dist/apps/design-system-dashboard/scoring/token-architecture.d.ts.map +1 -0
- package/dist/apps/design-system-dashboard/scoring/token-architecture.js +350 -0
- package/dist/apps/design-system-dashboard/scoring/token-architecture.js.map +1 -0
- package/dist/apps/design-system-dashboard/scoring/types.d.ts +89 -0
- package/dist/apps/design-system-dashboard/scoring/types.d.ts.map +1 -0
- package/dist/apps/design-system-dashboard/scoring/types.js +41 -0
- package/dist/apps/design-system-dashboard/scoring/types.js.map +1 -0
- package/dist/apps/design-system-dashboard/server.d.ts +24 -0
- package/dist/apps/design-system-dashboard/server.d.ts.map +1 -0
- package/dist/apps/design-system-dashboard/server.js +160 -0
- package/dist/apps/design-system-dashboard/server.js.map +1 -0
- package/dist/apps/token-browser/server.d.ts +26 -0
- package/dist/apps/token-browser/server.d.ts.map +1 -0
- package/dist/apps/token-browser/server.js +137 -0
- package/dist/apps/token-browser/server.js.map +1 -0
- package/dist/browser/base.d.ts +58 -0
- package/dist/browser/base.d.ts.map +1 -0
- package/dist/browser/base.js +6 -0
- package/dist/browser/base.js.map +1 -0
- package/dist/browser/local.d.ts +87 -0
- package/dist/browser/local.d.ts.map +1 -0
- package/dist/browser/local.js +318 -0
- package/dist/browser/local.js.map +1 -0
- package/dist/cloudflare/apps/design-system-dashboard/scoring/accessibility.js +277 -0
- package/dist/cloudflare/apps/design-system-dashboard/scoring/component-metadata.js +357 -0
- package/dist/cloudflare/apps/design-system-dashboard/scoring/consistency.js +341 -0
- package/dist/cloudflare/apps/design-system-dashboard/scoring/coverage.js +230 -0
- package/dist/cloudflare/apps/design-system-dashboard/scoring/engine.js +92 -0
- package/dist/cloudflare/apps/design-system-dashboard/scoring/naming-semantics.js +308 -0
- package/dist/cloudflare/apps/design-system-dashboard/scoring/token-architecture.js +349 -0
- package/dist/cloudflare/apps/design-system-dashboard/scoring/types.js +40 -0
- package/dist/cloudflare/apps/design-system-dashboard/server.js +159 -0
- package/dist/cloudflare/apps/token-browser/server.js +136 -0
- package/dist/cloudflare/browser/base.js +5 -0
- package/dist/cloudflare/browser/cloudflare.js +156 -0
- package/dist/cloudflare/browser-manager.js +157 -0
- package/dist/cloudflare/core/cloud-websocket-connector.js +267 -0
- package/dist/cloudflare/core/cloud-websocket-relay.js +199 -0
- package/dist/cloudflare/core/comment-tools.js +292 -0
- package/dist/cloudflare/core/config.js +161 -0
- package/dist/cloudflare/core/console-monitor.js +427 -0
- package/dist/cloudflare/core/design-code-tools.js +2504 -0
- package/dist/cloudflare/core/design-system-manifest.js +260 -0
- package/dist/cloudflare/core/design-system-tools.js +863 -0
- package/dist/cloudflare/core/enrichment/enrichment-service.js +272 -0
- package/dist/cloudflare/core/enrichment/index.js +7 -0
- package/dist/cloudflare/core/enrichment/relationship-mapper.js +351 -0
- package/dist/cloudflare/core/enrichment/style-resolver.js +326 -0
- package/dist/cloudflare/core/figma-api.js +409 -0
- package/dist/cloudflare/core/figma-connector.js +7 -0
- package/dist/cloudflare/core/figma-desktop-connector.js +1184 -0
- package/dist/cloudflare/core/figma-reconstruction-spec.js +402 -0
- package/dist/cloudflare/core/figma-style-extractor.js +311 -0
- package/dist/cloudflare/core/figma-tools.js +2947 -0
- package/dist/cloudflare/core/logger.js +53 -0
- package/dist/cloudflare/core/port-discovery.js +282 -0
- package/dist/cloudflare/core/snippet-injector.js +96 -0
- package/dist/cloudflare/core/types/design-code.js +4 -0
- package/dist/cloudflare/core/types/enriched.js +5 -0
- package/dist/cloudflare/core/types/index.js +4 -0
- package/dist/cloudflare/core/websocket-connector.js +256 -0
- package/dist/cloudflare/core/websocket-server.js +646 -0
- package/dist/cloudflare/core/write-tools.js +2091 -0
- package/dist/cloudflare/index.js +2899 -0
- package/dist/cloudflare/test-browser.js +88 -0
- package/dist/core/comment-tools.d.ts +11 -0
- package/dist/core/comment-tools.d.ts.map +1 -0
- package/dist/core/comment-tools.js +293 -0
- package/dist/core/comment-tools.js.map +1 -0
- package/dist/core/config.d.ts +17 -0
- package/dist/core/config.d.ts.map +1 -0
- package/dist/core/config.js +162 -0
- package/dist/core/config.js.map +1 -0
- package/dist/core/console-monitor.d.ts +82 -0
- package/dist/core/console-monitor.d.ts.map +1 -0
- package/dist/core/console-monitor.js +428 -0
- package/dist/core/console-monitor.js.map +1 -0
- package/dist/core/design-code-tools.d.ts +127 -0
- package/dist/core/design-code-tools.d.ts.map +1 -0
- package/dist/core/design-code-tools.js +2505 -0
- package/dist/core/design-code-tools.js.map +1 -0
- package/dist/core/design-system-manifest.d.ts +272 -0
- package/dist/core/design-system-manifest.d.ts.map +1 -0
- package/dist/core/design-system-manifest.js +261 -0
- package/dist/core/design-system-manifest.js.map +1 -0
- package/dist/core/design-system-tools.d.ts +17 -0
- package/dist/core/design-system-tools.d.ts.map +1 -0
- package/dist/core/design-system-tools.js +864 -0
- package/dist/core/design-system-tools.js.map +1 -0
- package/dist/core/enrichment/enrichment-service.d.ts +52 -0
- package/dist/core/enrichment/enrichment-service.d.ts.map +1 -0
- package/dist/core/enrichment/enrichment-service.js +273 -0
- package/dist/core/enrichment/enrichment-service.js.map +1 -0
- package/dist/core/enrichment/index.d.ts +8 -0
- package/dist/core/enrichment/index.d.ts.map +1 -0
- package/dist/core/enrichment/index.js +8 -0
- package/dist/core/enrichment/index.js.map +1 -0
- package/dist/core/enrichment/relationship-mapper.d.ts +106 -0
- package/dist/core/enrichment/relationship-mapper.d.ts.map +1 -0
- package/dist/core/enrichment/relationship-mapper.js +352 -0
- package/dist/core/enrichment/relationship-mapper.js.map +1 -0
- package/dist/core/enrichment/style-resolver.d.ts +80 -0
- package/dist/core/enrichment/style-resolver.d.ts.map +1 -0
- package/dist/core/enrichment/style-resolver.js +327 -0
- package/dist/core/enrichment/style-resolver.js.map +1 -0
- package/dist/core/figma-api.d.ts +201 -0
- package/dist/core/figma-api.d.ts.map +1 -0
- package/dist/core/figma-api.js +410 -0
- package/dist/core/figma-api.js.map +1 -0
- package/dist/core/figma-connector.d.ts +48 -0
- package/dist/core/figma-connector.d.ts.map +1 -0
- package/dist/core/figma-connector.js +8 -0
- package/dist/core/figma-connector.js.map +1 -0
- package/dist/core/figma-desktop-connector.d.ts +265 -0
- package/dist/core/figma-desktop-connector.d.ts.map +1 -0
- package/dist/core/figma-desktop-connector.js +1184 -0
- package/dist/core/figma-desktop-connector.js.map +1 -0
- package/dist/core/figma-reconstruction-spec.d.ts +166 -0
- package/dist/core/figma-reconstruction-spec.d.ts.map +1 -0
- package/dist/core/figma-reconstruction-spec.js +403 -0
- package/dist/core/figma-reconstruction-spec.js.map +1 -0
- package/dist/core/figma-style-extractor.d.ts +76 -0
- package/dist/core/figma-style-extractor.d.ts.map +1 -0
- package/dist/core/figma-style-extractor.js +312 -0
- package/dist/core/figma-style-extractor.js.map +1 -0
- package/dist/core/figma-tools.d.ts +23 -0
- package/dist/core/figma-tools.d.ts.map +1 -0
- package/dist/core/figma-tools.js +2948 -0
- package/dist/core/figma-tools.js.map +1 -0
- package/dist/core/logger.d.ts +22 -0
- package/dist/core/logger.d.ts.map +1 -0
- package/dist/core/logger.js +54 -0
- package/dist/core/logger.js.map +1 -0
- package/dist/core/port-discovery.d.ts +110 -0
- package/dist/core/port-discovery.d.ts.map +1 -0
- package/dist/core/port-discovery.js +283 -0
- package/dist/core/port-discovery.js.map +1 -0
- package/dist/core/snippet-injector.d.ts +24 -0
- package/dist/core/snippet-injector.d.ts.map +1 -0
- package/dist/core/snippet-injector.js +97 -0
- package/dist/core/snippet-injector.js.map +1 -0
- package/dist/core/types/design-code.d.ts +262 -0
- package/dist/core/types/design-code.d.ts.map +1 -0
- package/dist/core/types/design-code.js +5 -0
- package/dist/core/types/design-code.js.map +1 -0
- package/dist/core/types/enriched.d.ts +213 -0
- package/dist/core/types/enriched.d.ts.map +1 -0
- package/dist/core/types/enriched.js +6 -0
- package/dist/core/types/enriched.js.map +1 -0
- package/dist/core/types/index.d.ts +112 -0
- package/dist/core/types/index.d.ts.map +1 -0
- package/dist/core/types/index.js +5 -0
- package/dist/core/types/index.js.map +1 -0
- package/dist/core/websocket-connector.d.ts +55 -0
- package/dist/core/websocket-connector.d.ts.map +1 -0
- package/dist/core/websocket-connector.js +257 -0
- package/dist/core/websocket-connector.js.map +1 -0
- package/dist/core/websocket-server.d.ts +191 -0
- package/dist/core/websocket-server.d.ts.map +1 -0
- package/dist/core/websocket-server.js +647 -0
- package/dist/core/websocket-server.js.map +1 -0
- package/dist/core/write-tools.d.ts +7 -0
- package/dist/core/write-tools.d.ts.map +1 -0
- package/dist/core/write-tools.js +2092 -0
- package/dist/core/write-tools.js.map +1 -0
- package/dist/local.d.ts +84 -0
- package/dist/local.d.ts.map +1 -0
- package/dist/local.js +5039 -0
- package/dist/local.js.map +1 -0
- package/figma-desktop-bridge/README.md +313 -0
- package/figma-desktop-bridge/code.js +2818 -0
- package/figma-desktop-bridge/manifest.json +67 -0
- package/figma-desktop-bridge/ui.html +1236 -0
- package/package.json +87 -0
|
@@ -0,0 +1,272 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Enrichment Service
|
|
3
|
+
* Coordinates enrichment of Figma API responses
|
|
4
|
+
*/
|
|
5
|
+
import { StyleValueResolver } from "./style-resolver.js";
|
|
6
|
+
import { RelationshipMapper } from "./relationship-mapper.js";
|
|
7
|
+
export class EnrichmentService {
|
|
8
|
+
constructor(logger) {
|
|
9
|
+
// Caches
|
|
10
|
+
this.fileDataCache = new Map();
|
|
11
|
+
this.lastEnrichmentTime = new Map();
|
|
12
|
+
this.CACHE_TTL_MS = 5 * 60 * 1000; // 5 minutes
|
|
13
|
+
this.logger = logger;
|
|
14
|
+
this.styleResolver = new StyleValueResolver(logger);
|
|
15
|
+
this.relationshipMapper = new RelationshipMapper(logger);
|
|
16
|
+
}
|
|
17
|
+
/**
|
|
18
|
+
* Enrich styles response
|
|
19
|
+
*/
|
|
20
|
+
async enrichStyles(styles, fileKey, options = {}, fileData) {
|
|
21
|
+
if (!options.enrich) {
|
|
22
|
+
return styles; // Return as-is if enrichment not requested
|
|
23
|
+
}
|
|
24
|
+
this.logger.info({ fileKey, count: styles.length }, "Enriching styles");
|
|
25
|
+
try {
|
|
26
|
+
// Get file data for relationships (use provided data if available)
|
|
27
|
+
const data = fileData || await this.getFileDataForEnrichment(fileKey);
|
|
28
|
+
const variables = this.extractVariablesMap(data);
|
|
29
|
+
// Build relationships if requested
|
|
30
|
+
if (options.include_usage) {
|
|
31
|
+
await this.relationshipMapper.buildRelationships(data);
|
|
32
|
+
}
|
|
33
|
+
const enrichedStyles = [];
|
|
34
|
+
for (const style of styles) {
|
|
35
|
+
const enriched = {
|
|
36
|
+
...style,
|
|
37
|
+
};
|
|
38
|
+
// Resolve value and variable reference
|
|
39
|
+
if (options.include_exports !== false) {
|
|
40
|
+
const { value, variableRef } = await this.styleResolver.resolveStyleValue(style, variables, options.max_depth);
|
|
41
|
+
enriched.resolved_value = value;
|
|
42
|
+
enriched.variable_reference = variableRef;
|
|
43
|
+
// Generate export formats
|
|
44
|
+
if (value && options.export_formats) {
|
|
45
|
+
enriched.export_formats = this.styleResolver.generateExportFormats(style.name, value, style.style_type, options.export_formats);
|
|
46
|
+
}
|
|
47
|
+
}
|
|
48
|
+
// Add usage information
|
|
49
|
+
if (options.include_usage) {
|
|
50
|
+
const styleId = style.node_id || style.key || style.id;
|
|
51
|
+
enriched.used_in_components =
|
|
52
|
+
this.relationshipMapper.getComponentsByStyle(styleId);
|
|
53
|
+
enriched.usage_count =
|
|
54
|
+
this.relationshipMapper.getStyleUsageCount(styleId);
|
|
55
|
+
}
|
|
56
|
+
enrichedStyles.push(enriched);
|
|
57
|
+
}
|
|
58
|
+
this.logger.info({ enrichedCount: enrichedStyles.length }, "Styles enrichment complete");
|
|
59
|
+
return enrichedStyles;
|
|
60
|
+
}
|
|
61
|
+
catch (error) {
|
|
62
|
+
this.logger.error({ error }, "Failed to enrich styles");
|
|
63
|
+
return styles; // Return original on error
|
|
64
|
+
}
|
|
65
|
+
}
|
|
66
|
+
/**
|
|
67
|
+
* Enrich variables response
|
|
68
|
+
*/
|
|
69
|
+
async enrichVariables(variables, fileKey, options = {}, fileData) {
|
|
70
|
+
if (!options.enrich) {
|
|
71
|
+
return variables;
|
|
72
|
+
}
|
|
73
|
+
this.logger.info({ fileKey, count: variables.length }, "Enriching variables");
|
|
74
|
+
try {
|
|
75
|
+
const data = fileData || await this.getFileDataForEnrichment(fileKey);
|
|
76
|
+
const variablesMap = this.extractVariablesMap(data);
|
|
77
|
+
// Build relationships if requested
|
|
78
|
+
if (options.include_usage) {
|
|
79
|
+
await this.relationshipMapper.buildRelationships(data);
|
|
80
|
+
}
|
|
81
|
+
const enrichedVars = [];
|
|
82
|
+
for (const variable of variables) {
|
|
83
|
+
const enriched = {
|
|
84
|
+
...variable,
|
|
85
|
+
};
|
|
86
|
+
// Resolve values for all modes
|
|
87
|
+
if (options.include_exports !== false) {
|
|
88
|
+
const resolved_values = {};
|
|
89
|
+
for (const [modeId, value] of Object.entries(variable.valuesByMode || {})) {
|
|
90
|
+
const resolvedValue = await this.styleResolver.resolveVariableValue(variable, variablesMap, options.max_depth);
|
|
91
|
+
resolved_values[modeId] = resolvedValue;
|
|
92
|
+
}
|
|
93
|
+
enriched.resolved_values = resolved_values;
|
|
94
|
+
// Generate export formats using first mode
|
|
95
|
+
const firstModeValue = Object.values(resolved_values)[0];
|
|
96
|
+
if (firstModeValue && options.export_formats) {
|
|
97
|
+
enriched.export_formats = this.styleResolver.generateExportFormats(variable.name, firstModeValue, variable.resolvedType, options.export_formats);
|
|
98
|
+
}
|
|
99
|
+
}
|
|
100
|
+
// Add usage information
|
|
101
|
+
if (options.include_usage) {
|
|
102
|
+
enriched.used_in_styles =
|
|
103
|
+
this.relationshipMapper.getStylesByVariable(variable.id);
|
|
104
|
+
enriched.used_in_components =
|
|
105
|
+
this.relationshipMapper.getComponentsByVariable(variable.id);
|
|
106
|
+
enriched.usage_count =
|
|
107
|
+
this.relationshipMapper.getVariableUsageCount(variable.id);
|
|
108
|
+
}
|
|
109
|
+
// Add dependencies
|
|
110
|
+
if (options.include_dependencies) {
|
|
111
|
+
enriched.dependencies =
|
|
112
|
+
this.relationshipMapper.getVariableDependencies(variable.id);
|
|
113
|
+
}
|
|
114
|
+
enrichedVars.push(enriched);
|
|
115
|
+
}
|
|
116
|
+
this.logger.info({ enrichedCount: enrichedVars.length }, "Variables enrichment complete");
|
|
117
|
+
return enrichedVars;
|
|
118
|
+
}
|
|
119
|
+
catch (error) {
|
|
120
|
+
this.logger.error({ error }, "Failed to enrich variables");
|
|
121
|
+
return variables;
|
|
122
|
+
}
|
|
123
|
+
}
|
|
124
|
+
/**
|
|
125
|
+
* Enrich component response
|
|
126
|
+
*/
|
|
127
|
+
async enrichComponent(component, fileKey, options = {}, fileData) {
|
|
128
|
+
if (!options.enrich) {
|
|
129
|
+
return component;
|
|
130
|
+
}
|
|
131
|
+
this.logger.info({ fileKey, componentId: component.id }, "Enriching component");
|
|
132
|
+
try {
|
|
133
|
+
const enriched = {
|
|
134
|
+
...component,
|
|
135
|
+
};
|
|
136
|
+
const data = fileData || await this.getFileDataForEnrichment(fileKey);
|
|
137
|
+
// Build relationships
|
|
138
|
+
await this.relationshipMapper.buildRelationships(data);
|
|
139
|
+
// Extract styles used
|
|
140
|
+
if (component.styles) {
|
|
141
|
+
const stylesUsed = [];
|
|
142
|
+
for (const [prop, styleId] of Object.entries(component.styles)) {
|
|
143
|
+
const style = data.styles?.[styleId];
|
|
144
|
+
if (style) {
|
|
145
|
+
stylesUsed.push({
|
|
146
|
+
style_id: styleId,
|
|
147
|
+
style_name: style.name,
|
|
148
|
+
style_type: style.style_type,
|
|
149
|
+
property: prop,
|
|
150
|
+
});
|
|
151
|
+
}
|
|
152
|
+
}
|
|
153
|
+
enriched.styles_used = stylesUsed;
|
|
154
|
+
}
|
|
155
|
+
// Extract variables used
|
|
156
|
+
if (component.boundVariables) {
|
|
157
|
+
const varsUsed = [];
|
|
158
|
+
// This would need actual variable resolution
|
|
159
|
+
enriched.variables_used = varsUsed;
|
|
160
|
+
}
|
|
161
|
+
// Detect hardcoded values (simplified for now)
|
|
162
|
+
// TODO: Implement full hardcoded value detection
|
|
163
|
+
enriched.hardcoded_values = [];
|
|
164
|
+
// Calculate token coverage
|
|
165
|
+
const totalProps = (enriched.styles_used?.length || 0) + (enriched.variables_used?.length || 0) + (enriched.hardcoded_values?.length || 0);
|
|
166
|
+
const tokenProps = (enriched.styles_used?.length || 0) + (enriched.variables_used?.length || 0);
|
|
167
|
+
enriched.token_coverage =
|
|
168
|
+
totalProps > 0 ? Math.round((tokenProps / totalProps) * 100) : 0;
|
|
169
|
+
this.logger.info({ componentId: component.id, coverage: enriched.token_coverage }, "Component enrichment complete");
|
|
170
|
+
return enriched;
|
|
171
|
+
}
|
|
172
|
+
catch (error) {
|
|
173
|
+
this.logger.error({ error }, "Failed to enrich component");
|
|
174
|
+
return component;
|
|
175
|
+
}
|
|
176
|
+
}
|
|
177
|
+
/**
|
|
178
|
+
* Enrich file data response
|
|
179
|
+
*/
|
|
180
|
+
async enrichFileData(fileData, options = {}) {
|
|
181
|
+
if (!options.enrich) {
|
|
182
|
+
return fileData;
|
|
183
|
+
}
|
|
184
|
+
this.logger.info({ fileKey: fileData.fileKey }, "Enriching file data");
|
|
185
|
+
try {
|
|
186
|
+
const enriched = {
|
|
187
|
+
...fileData,
|
|
188
|
+
};
|
|
189
|
+
// Build relationships for statistics
|
|
190
|
+
await this.relationshipMapper.buildRelationships(fileData);
|
|
191
|
+
// Calculate statistics
|
|
192
|
+
const styles = Object.values(fileData.styles || {});
|
|
193
|
+
const variables = Array.from(this.extractVariablesMap(fileData).values());
|
|
194
|
+
const unusedStyles = this.relationshipMapper.getUnusedStyles(styles);
|
|
195
|
+
const unusedVars = this.relationshipMapper.getUnusedVariables(variables);
|
|
196
|
+
enriched.statistics = {
|
|
197
|
+
total_variables: variables.length,
|
|
198
|
+
total_styles: styles.length,
|
|
199
|
+
total_components: Object.keys(fileData.components || {}).length,
|
|
200
|
+
unused_variables: unusedVars.length,
|
|
201
|
+
unused_styles: unusedStyles.length,
|
|
202
|
+
average_token_coverage: 0, // TODO: Calculate from components
|
|
203
|
+
total_hardcoded_values: 0, // TODO: Calculate from components
|
|
204
|
+
audit_issues_count: 0, // TODO: Calculate from audit
|
|
205
|
+
};
|
|
206
|
+
// Calculate health score (simple formula)
|
|
207
|
+
const usageRate = variables.length > 0
|
|
208
|
+
? ((variables.length - unusedVars.length) / variables.length) * 100
|
|
209
|
+
: 100;
|
|
210
|
+
enriched.health_score = Math.round(usageRate);
|
|
211
|
+
this.logger.info({ health_score: enriched.health_score }, "File data enrichment complete");
|
|
212
|
+
return enriched;
|
|
213
|
+
}
|
|
214
|
+
catch (error) {
|
|
215
|
+
this.logger.error({ error }, "Failed to enrich file data");
|
|
216
|
+
return fileData;
|
|
217
|
+
}
|
|
218
|
+
}
|
|
219
|
+
/**
|
|
220
|
+
* Get file data for enrichment (with caching)
|
|
221
|
+
* NOTE: This is a placeholder that returns the data passed in
|
|
222
|
+
* In a full implementation, this would fetch fresh data from Figma API
|
|
223
|
+
*/
|
|
224
|
+
async getFileDataForEnrichment(fileKey, providedData) {
|
|
225
|
+
const now = Date.now();
|
|
226
|
+
const lastEnrich = this.lastEnrichmentTime.get(fileKey) || 0;
|
|
227
|
+
// Return cached if fresh
|
|
228
|
+
if (this.fileDataCache.has(fileKey) &&
|
|
229
|
+
now - lastEnrich < this.CACHE_TTL_MS) {
|
|
230
|
+
return this.fileDataCache.get(fileKey);
|
|
231
|
+
}
|
|
232
|
+
// If data is provided, use it (from the tool that called us)
|
|
233
|
+
// This avoids redundant API calls since the tool already fetched the data
|
|
234
|
+
const fileData = providedData || {
|
|
235
|
+
styles: {},
|
|
236
|
+
variables: {},
|
|
237
|
+
document: {},
|
|
238
|
+
components: {},
|
|
239
|
+
};
|
|
240
|
+
this.fileDataCache.set(fileKey, fileData);
|
|
241
|
+
this.lastEnrichmentTime.set(fileKey, now);
|
|
242
|
+
return fileData;
|
|
243
|
+
}
|
|
244
|
+
/**
|
|
245
|
+
* Set file data in cache (called by tools that already have the data)
|
|
246
|
+
*/
|
|
247
|
+
setFileDataCache(fileKey, fileData) {
|
|
248
|
+
this.fileDataCache.set(fileKey, fileData);
|
|
249
|
+
this.lastEnrichmentTime.set(fileKey, Date.now());
|
|
250
|
+
}
|
|
251
|
+
/**
|
|
252
|
+
* Extract variables as a Map for efficient lookup
|
|
253
|
+
*/
|
|
254
|
+
extractVariablesMap(fileData) {
|
|
255
|
+
const variablesMap = new Map();
|
|
256
|
+
if (fileData.variables) {
|
|
257
|
+
for (const [id, variable] of Object.entries(fileData.variables)) {
|
|
258
|
+
variablesMap.set(id, variable);
|
|
259
|
+
}
|
|
260
|
+
}
|
|
261
|
+
return variablesMap;
|
|
262
|
+
}
|
|
263
|
+
/**
|
|
264
|
+
* Clear all caches
|
|
265
|
+
*/
|
|
266
|
+
clearCache() {
|
|
267
|
+
this.fileDataCache.clear();
|
|
268
|
+
this.lastEnrichmentTime.clear();
|
|
269
|
+
this.styleResolver.clearCache();
|
|
270
|
+
this.relationshipMapper.clear();
|
|
271
|
+
}
|
|
272
|
+
}
|
|
@@ -0,0 +1,7 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Enrichment module exports
|
|
3
|
+
* Phase 5: Enriched Data Extraction & Design System Auditing
|
|
4
|
+
*/
|
|
5
|
+
export { StyleValueResolver } from "./style-resolver.js";
|
|
6
|
+
export { RelationshipMapper } from "./relationship-mapper.js";
|
|
7
|
+
export { EnrichmentService } from "./enrichment-service.js";
|
|
@@ -0,0 +1,351 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Relationship Mapper
|
|
3
|
+
* Maps relationships between styles, variables, and components
|
|
4
|
+
* Tracks usage counts and builds reverse lookup indexes
|
|
5
|
+
*/
|
|
6
|
+
export class RelationshipMapper {
|
|
7
|
+
constructor(logger) {
|
|
8
|
+
// Relationship indexes
|
|
9
|
+
this.componentsByStyle = new Map();
|
|
10
|
+
this.componentsByVariable = new Map();
|
|
11
|
+
this.stylesByVariable = new Map();
|
|
12
|
+
this.variableDependencies = new Map();
|
|
13
|
+
// Usage counters
|
|
14
|
+
this.styleUsageCount = new Map();
|
|
15
|
+
this.variableUsageCount = new Map();
|
|
16
|
+
this.logger = logger;
|
|
17
|
+
}
|
|
18
|
+
/**
|
|
19
|
+
* Build all relationship indexes from file data
|
|
20
|
+
*/
|
|
21
|
+
async buildRelationships(fileData) {
|
|
22
|
+
this.logger.info("Building relationship indexes");
|
|
23
|
+
try {
|
|
24
|
+
// Clear existing indexes
|
|
25
|
+
this.clear();
|
|
26
|
+
// Build variable dependencies first
|
|
27
|
+
if (fileData.variables) {
|
|
28
|
+
await this.buildVariableDependencies(fileData.variables);
|
|
29
|
+
}
|
|
30
|
+
// Build style-variable relationships
|
|
31
|
+
if (fileData.styles && fileData.variables) {
|
|
32
|
+
await this.buildStyleVariableRelationships(fileData.styles, fileData.variables);
|
|
33
|
+
}
|
|
34
|
+
// Build component relationships
|
|
35
|
+
if (fileData.document) {
|
|
36
|
+
await this.buildComponentRelationships(fileData.document);
|
|
37
|
+
}
|
|
38
|
+
this.logger.info({
|
|
39
|
+
styles: this.componentsByStyle.size,
|
|
40
|
+
variables: this.componentsByVariable.size,
|
|
41
|
+
}, "Relationship indexes built successfully");
|
|
42
|
+
}
|
|
43
|
+
catch (error) {
|
|
44
|
+
this.logger.error({ error }, "Error building relationships");
|
|
45
|
+
throw error;
|
|
46
|
+
}
|
|
47
|
+
}
|
|
48
|
+
/**
|
|
49
|
+
* Get components that use a specific style
|
|
50
|
+
*/
|
|
51
|
+
getComponentsByStyle(styleId) {
|
|
52
|
+
return this.componentsByStyle.get(styleId) || [];
|
|
53
|
+
}
|
|
54
|
+
/**
|
|
55
|
+
* Get components that use a specific variable
|
|
56
|
+
*/
|
|
57
|
+
getComponentsByVariable(variableId) {
|
|
58
|
+
return this.componentsByVariable.get(variableId) || [];
|
|
59
|
+
}
|
|
60
|
+
/**
|
|
61
|
+
* Get styles that use a specific variable
|
|
62
|
+
*/
|
|
63
|
+
getStylesByVariable(variableId) {
|
|
64
|
+
return this.stylesByVariable.get(variableId) || [];
|
|
65
|
+
}
|
|
66
|
+
/**
|
|
67
|
+
* Get variable dependencies (what variables this variable references)
|
|
68
|
+
*/
|
|
69
|
+
getVariableDependencies(variableId) {
|
|
70
|
+
return this.variableDependencies.get(variableId) || [];
|
|
71
|
+
}
|
|
72
|
+
/**
|
|
73
|
+
* Get usage count for a style
|
|
74
|
+
*/
|
|
75
|
+
getStyleUsageCount(styleId) {
|
|
76
|
+
return this.styleUsageCount.get(styleId) || 0;
|
|
77
|
+
}
|
|
78
|
+
/**
|
|
79
|
+
* Get usage count for a variable
|
|
80
|
+
*/
|
|
81
|
+
getVariableUsageCount(variableId) {
|
|
82
|
+
return this.variableUsageCount.get(variableId) || 0;
|
|
83
|
+
}
|
|
84
|
+
/**
|
|
85
|
+
* Find unused styles (styles with zero usage)
|
|
86
|
+
*/
|
|
87
|
+
getUnusedStyles(allStyles) {
|
|
88
|
+
const unused = [];
|
|
89
|
+
for (const style of allStyles) {
|
|
90
|
+
const usageCount = this.getStyleUsageCount(style.node_id || style.key || style.id);
|
|
91
|
+
if (usageCount === 0) {
|
|
92
|
+
unused.push({
|
|
93
|
+
id: style.id || style.key,
|
|
94
|
+
name: style.name,
|
|
95
|
+
type: style.style_type || style.styleType,
|
|
96
|
+
node_id: style.node_id || style.key,
|
|
97
|
+
});
|
|
98
|
+
}
|
|
99
|
+
}
|
|
100
|
+
return unused;
|
|
101
|
+
}
|
|
102
|
+
/**
|
|
103
|
+
* Find unused variables (variables with zero usage)
|
|
104
|
+
*/
|
|
105
|
+
getUnusedVariables(allVariables) {
|
|
106
|
+
const unused = [];
|
|
107
|
+
for (const variable of allVariables) {
|
|
108
|
+
const usageCount = this.getVariableUsageCount(variable.id);
|
|
109
|
+
if (usageCount === 0) {
|
|
110
|
+
unused.push({
|
|
111
|
+
id: variable.id,
|
|
112
|
+
name: variable.name,
|
|
113
|
+
collection: variable.variableCollectionId,
|
|
114
|
+
resolvedType: variable.resolvedType,
|
|
115
|
+
});
|
|
116
|
+
}
|
|
117
|
+
}
|
|
118
|
+
return unused;
|
|
119
|
+
}
|
|
120
|
+
/**
|
|
121
|
+
* Build variable dependency graph (which variables reference which)
|
|
122
|
+
*/
|
|
123
|
+
async buildVariableDependencies(variables) {
|
|
124
|
+
for (const [variableId, variable] of variables.entries()) {
|
|
125
|
+
const dependencies = this.extractVariableDependencies(variable, variables);
|
|
126
|
+
if (dependencies.length > 0) {
|
|
127
|
+
this.variableDependencies.set(variableId, dependencies);
|
|
128
|
+
}
|
|
129
|
+
}
|
|
130
|
+
}
|
|
131
|
+
/**
|
|
132
|
+
* Extract dependencies from a single variable
|
|
133
|
+
*/
|
|
134
|
+
extractVariableDependencies(variable, allVariables, depth = 0) {
|
|
135
|
+
const dependencies = [];
|
|
136
|
+
// Check all modes for variable aliases
|
|
137
|
+
for (const [modeId, value] of Object.entries(variable.valuesByMode || {})) {
|
|
138
|
+
if (typeof value === "object" &&
|
|
139
|
+
value !== null &&
|
|
140
|
+
value.type === "VARIABLE_ALIAS") {
|
|
141
|
+
const targetId = value.id;
|
|
142
|
+
const targetVariable = allVariables.get(targetId);
|
|
143
|
+
if (targetVariable) {
|
|
144
|
+
dependencies.push({
|
|
145
|
+
id: targetId,
|
|
146
|
+
name: targetVariable.name,
|
|
147
|
+
type: "alias",
|
|
148
|
+
depth,
|
|
149
|
+
});
|
|
150
|
+
// Increment usage count for the referenced variable
|
|
151
|
+
const currentCount = this.variableUsageCount.get(targetId) || 0;
|
|
152
|
+
this.variableUsageCount.set(targetId, currentCount + 1);
|
|
153
|
+
}
|
|
154
|
+
}
|
|
155
|
+
}
|
|
156
|
+
return dependencies;
|
|
157
|
+
}
|
|
158
|
+
/**
|
|
159
|
+
* Build relationships between styles and variables
|
|
160
|
+
*/
|
|
161
|
+
async buildStyleVariableRelationships(styles, variables) {
|
|
162
|
+
for (const style of styles) {
|
|
163
|
+
const variableRefs = this.extractStyleVariableReferences(style);
|
|
164
|
+
for (const varRef of variableRefs) {
|
|
165
|
+
// Track which styles use this variable
|
|
166
|
+
if (!this.stylesByVariable.has(varRef)) {
|
|
167
|
+
this.stylesByVariable.set(varRef, []);
|
|
168
|
+
}
|
|
169
|
+
this.stylesByVariable.get(varRef)?.push({
|
|
170
|
+
id: style.id || style.key,
|
|
171
|
+
name: style.name,
|
|
172
|
+
type: style.style_type || style.styleType,
|
|
173
|
+
node_id: style.node_id || style.key,
|
|
174
|
+
});
|
|
175
|
+
// Increment variable usage count
|
|
176
|
+
const currentCount = this.variableUsageCount.get(varRef) || 0;
|
|
177
|
+
this.variableUsageCount.set(varRef, currentCount + 1);
|
|
178
|
+
}
|
|
179
|
+
}
|
|
180
|
+
}
|
|
181
|
+
/**
|
|
182
|
+
* Extract variable references from a style
|
|
183
|
+
*/
|
|
184
|
+
extractStyleVariableReferences(style) {
|
|
185
|
+
const refs = [];
|
|
186
|
+
// Check boundVariables
|
|
187
|
+
if (style.boundVariables) {
|
|
188
|
+
const props = ["fills", "strokes", "effects", "text"];
|
|
189
|
+
for (const prop of props) {
|
|
190
|
+
if (style.boundVariables[prop]) {
|
|
191
|
+
const binding = style.boundVariables[prop];
|
|
192
|
+
if (Array.isArray(binding)) {
|
|
193
|
+
for (const b of binding) {
|
|
194
|
+
if (b.id)
|
|
195
|
+
refs.push(b.id);
|
|
196
|
+
}
|
|
197
|
+
}
|
|
198
|
+
else if (binding.id) {
|
|
199
|
+
refs.push(binding.id);
|
|
200
|
+
}
|
|
201
|
+
}
|
|
202
|
+
}
|
|
203
|
+
}
|
|
204
|
+
return refs;
|
|
205
|
+
}
|
|
206
|
+
/**
|
|
207
|
+
* Build component relationships by traversing the document tree
|
|
208
|
+
*/
|
|
209
|
+
async buildComponentRelationships(document) {
|
|
210
|
+
// Traverse all pages
|
|
211
|
+
if (document.children) {
|
|
212
|
+
for (const page of document.children) {
|
|
213
|
+
await this.traverseNode(page, page.name);
|
|
214
|
+
}
|
|
215
|
+
}
|
|
216
|
+
}
|
|
217
|
+
/**
|
|
218
|
+
* Recursively traverse nodes to find component instances and their style/variable usage
|
|
219
|
+
*/
|
|
220
|
+
async traverseNode(node, pageName, path = []) {
|
|
221
|
+
const currentPath = [...path, node.name];
|
|
222
|
+
// Check if this node uses any styles
|
|
223
|
+
if (node.styles) {
|
|
224
|
+
this.trackNodeStyleUsage(node, pageName, currentPath);
|
|
225
|
+
}
|
|
226
|
+
// Check if this node uses any variables (via boundVariables)
|
|
227
|
+
if (node.boundVariables) {
|
|
228
|
+
this.trackNodeVariableUsage(node, pageName, currentPath);
|
|
229
|
+
}
|
|
230
|
+
// Recurse into children
|
|
231
|
+
if (node.children) {
|
|
232
|
+
for (const child of node.children) {
|
|
233
|
+
await this.traverseNode(child, pageName, currentPath);
|
|
234
|
+
}
|
|
235
|
+
}
|
|
236
|
+
}
|
|
237
|
+
/**
|
|
238
|
+
* Track which styles a node uses
|
|
239
|
+
*/
|
|
240
|
+
trackNodeStyleUsage(node, pageName, path) {
|
|
241
|
+
const componentUsage = {
|
|
242
|
+
id: node.id,
|
|
243
|
+
name: node.name,
|
|
244
|
+
type: node.type,
|
|
245
|
+
page: pageName,
|
|
246
|
+
};
|
|
247
|
+
// Check all style properties (fill, stroke, text, effect, grid)
|
|
248
|
+
const styleProps = ["fill", "stroke", "text", "effect", "grid"];
|
|
249
|
+
for (const prop of styleProps) {
|
|
250
|
+
const styleId = node.styles?.[prop];
|
|
251
|
+
if (styleId) {
|
|
252
|
+
// Add to componentsByStyle index
|
|
253
|
+
if (!this.componentsByStyle.has(styleId)) {
|
|
254
|
+
this.componentsByStyle.set(styleId, []);
|
|
255
|
+
}
|
|
256
|
+
this.componentsByStyle.get(styleId)?.push(componentUsage);
|
|
257
|
+
// Increment style usage count
|
|
258
|
+
const currentCount = this.styleUsageCount.get(styleId) || 0;
|
|
259
|
+
this.styleUsageCount.set(styleId, currentCount + 1);
|
|
260
|
+
}
|
|
261
|
+
}
|
|
262
|
+
}
|
|
263
|
+
/**
|
|
264
|
+
* Track which variables a node uses
|
|
265
|
+
*/
|
|
266
|
+
trackNodeVariableUsage(node, pageName, path) {
|
|
267
|
+
const componentUsage = {
|
|
268
|
+
id: node.id,
|
|
269
|
+
name: node.name,
|
|
270
|
+
type: node.type,
|
|
271
|
+
page: pageName,
|
|
272
|
+
};
|
|
273
|
+
// Extract all variable IDs from boundVariables
|
|
274
|
+
const variableIds = this.extractNodeVariableReferences(node);
|
|
275
|
+
for (const varId of variableIds) {
|
|
276
|
+
// Add to componentsByVariable index
|
|
277
|
+
if (!this.componentsByVariable.has(varId)) {
|
|
278
|
+
this.componentsByVariable.set(varId, []);
|
|
279
|
+
}
|
|
280
|
+
this.componentsByVariable.get(varId)?.push(componentUsage);
|
|
281
|
+
// Increment variable usage count
|
|
282
|
+
const currentCount = this.variableUsageCount.get(varId) || 0;
|
|
283
|
+
this.variableUsageCount.set(varId, currentCount + 1);
|
|
284
|
+
}
|
|
285
|
+
}
|
|
286
|
+
/**
|
|
287
|
+
* Extract all variable references from a node's boundVariables
|
|
288
|
+
*/
|
|
289
|
+
extractNodeVariableReferences(node) {
|
|
290
|
+
const refs = [];
|
|
291
|
+
if (!node.boundVariables)
|
|
292
|
+
return refs;
|
|
293
|
+
// boundVariables can have many properties (fills, strokes, etc.)
|
|
294
|
+
for (const [prop, binding] of Object.entries(node.boundVariables)) {
|
|
295
|
+
if (Array.isArray(binding)) {
|
|
296
|
+
for (const b of binding) {
|
|
297
|
+
if (b.id)
|
|
298
|
+
refs.push(b.id);
|
|
299
|
+
}
|
|
300
|
+
}
|
|
301
|
+
else if (binding && typeof binding === "object" && binding.id) {
|
|
302
|
+
refs.push(binding.id);
|
|
303
|
+
}
|
|
304
|
+
}
|
|
305
|
+
return refs;
|
|
306
|
+
}
|
|
307
|
+
/**
|
|
308
|
+
* Detect circular variable references
|
|
309
|
+
*/
|
|
310
|
+
detectCircularReferences() {
|
|
311
|
+
const circular = [];
|
|
312
|
+
const visited = new Set();
|
|
313
|
+
const currentPath = [];
|
|
314
|
+
for (const [variableId] of this.variableDependencies) {
|
|
315
|
+
this.detectCircularDFS(variableId, visited, currentPath, circular);
|
|
316
|
+
}
|
|
317
|
+
return circular;
|
|
318
|
+
}
|
|
319
|
+
/**
|
|
320
|
+
* DFS helper for detecting circular references
|
|
321
|
+
*/
|
|
322
|
+
detectCircularDFS(variableId, visited, currentPath, circular) {
|
|
323
|
+
if (currentPath.includes(variableId)) {
|
|
324
|
+
// Found a cycle
|
|
325
|
+
const cycleStart = currentPath.indexOf(variableId);
|
|
326
|
+
const cycle = currentPath.slice(cycleStart).concat(variableId);
|
|
327
|
+
circular.push({ chain: cycle });
|
|
328
|
+
return;
|
|
329
|
+
}
|
|
330
|
+
if (visited.has(variableId))
|
|
331
|
+
return;
|
|
332
|
+
visited.add(variableId);
|
|
333
|
+
currentPath.push(variableId);
|
|
334
|
+
const dependencies = this.variableDependencies.get(variableId) || [];
|
|
335
|
+
for (const dep of dependencies) {
|
|
336
|
+
this.detectCircularDFS(dep.id, visited, currentPath, circular);
|
|
337
|
+
}
|
|
338
|
+
currentPath.pop();
|
|
339
|
+
}
|
|
340
|
+
/**
|
|
341
|
+
* Clear all indexes and caches
|
|
342
|
+
*/
|
|
343
|
+
clear() {
|
|
344
|
+
this.componentsByStyle.clear();
|
|
345
|
+
this.componentsByVariable.clear();
|
|
346
|
+
this.stylesByVariable.clear();
|
|
347
|
+
this.variableDependencies.clear();
|
|
348
|
+
this.styleUsageCount.clear();
|
|
349
|
+
this.variableUsageCount.clear();
|
|
350
|
+
}
|
|
351
|
+
}
|