@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.
Files changed (201) hide show
  1. package/LICENSE +21 -0
  2. package/README.md +816 -0
  3. package/dist/apps/design-system-dashboard/scoring/accessibility.d.ts +14 -0
  4. package/dist/apps/design-system-dashboard/scoring/accessibility.d.ts.map +1 -0
  5. package/dist/apps/design-system-dashboard/scoring/accessibility.js +278 -0
  6. package/dist/apps/design-system-dashboard/scoring/accessibility.js.map +1 -0
  7. package/dist/apps/design-system-dashboard/scoring/component-metadata.d.ts +29 -0
  8. package/dist/apps/design-system-dashboard/scoring/component-metadata.d.ts.map +1 -0
  9. package/dist/apps/design-system-dashboard/scoring/component-metadata.js +358 -0
  10. package/dist/apps/design-system-dashboard/scoring/component-metadata.js.map +1 -0
  11. package/dist/apps/design-system-dashboard/scoring/consistency.d.ts +14 -0
  12. package/dist/apps/design-system-dashboard/scoring/consistency.d.ts.map +1 -0
  13. package/dist/apps/design-system-dashboard/scoring/consistency.js +342 -0
  14. package/dist/apps/design-system-dashboard/scoring/consistency.js.map +1 -0
  15. package/dist/apps/design-system-dashboard/scoring/coverage.d.ts +14 -0
  16. package/dist/apps/design-system-dashboard/scoring/coverage.d.ts.map +1 -0
  17. package/dist/apps/design-system-dashboard/scoring/coverage.js +231 -0
  18. package/dist/apps/design-system-dashboard/scoring/coverage.js.map +1 -0
  19. package/dist/apps/design-system-dashboard/scoring/engine.d.ts +27 -0
  20. package/dist/apps/design-system-dashboard/scoring/engine.d.ts.map +1 -0
  21. package/dist/apps/design-system-dashboard/scoring/engine.js +93 -0
  22. package/dist/apps/design-system-dashboard/scoring/engine.js.map +1 -0
  23. package/dist/apps/design-system-dashboard/scoring/naming-semantics.d.ts +14 -0
  24. package/dist/apps/design-system-dashboard/scoring/naming-semantics.d.ts.map +1 -0
  25. package/dist/apps/design-system-dashboard/scoring/naming-semantics.js +309 -0
  26. package/dist/apps/design-system-dashboard/scoring/naming-semantics.js.map +1 -0
  27. package/dist/apps/design-system-dashboard/scoring/token-architecture.d.ts +14 -0
  28. package/dist/apps/design-system-dashboard/scoring/token-architecture.d.ts.map +1 -0
  29. package/dist/apps/design-system-dashboard/scoring/token-architecture.js +350 -0
  30. package/dist/apps/design-system-dashboard/scoring/token-architecture.js.map +1 -0
  31. package/dist/apps/design-system-dashboard/scoring/types.d.ts +89 -0
  32. package/dist/apps/design-system-dashboard/scoring/types.d.ts.map +1 -0
  33. package/dist/apps/design-system-dashboard/scoring/types.js +41 -0
  34. package/dist/apps/design-system-dashboard/scoring/types.js.map +1 -0
  35. package/dist/apps/design-system-dashboard/server.d.ts +24 -0
  36. package/dist/apps/design-system-dashboard/server.d.ts.map +1 -0
  37. package/dist/apps/design-system-dashboard/server.js +160 -0
  38. package/dist/apps/design-system-dashboard/server.js.map +1 -0
  39. package/dist/apps/token-browser/server.d.ts +26 -0
  40. package/dist/apps/token-browser/server.d.ts.map +1 -0
  41. package/dist/apps/token-browser/server.js +137 -0
  42. package/dist/apps/token-browser/server.js.map +1 -0
  43. package/dist/browser/base.d.ts +58 -0
  44. package/dist/browser/base.d.ts.map +1 -0
  45. package/dist/browser/base.js +6 -0
  46. package/dist/browser/base.js.map +1 -0
  47. package/dist/browser/local.d.ts +87 -0
  48. package/dist/browser/local.d.ts.map +1 -0
  49. package/dist/browser/local.js +318 -0
  50. package/dist/browser/local.js.map +1 -0
  51. package/dist/cloudflare/apps/design-system-dashboard/scoring/accessibility.js +277 -0
  52. package/dist/cloudflare/apps/design-system-dashboard/scoring/component-metadata.js +357 -0
  53. package/dist/cloudflare/apps/design-system-dashboard/scoring/consistency.js +341 -0
  54. package/dist/cloudflare/apps/design-system-dashboard/scoring/coverage.js +230 -0
  55. package/dist/cloudflare/apps/design-system-dashboard/scoring/engine.js +92 -0
  56. package/dist/cloudflare/apps/design-system-dashboard/scoring/naming-semantics.js +308 -0
  57. package/dist/cloudflare/apps/design-system-dashboard/scoring/token-architecture.js +349 -0
  58. package/dist/cloudflare/apps/design-system-dashboard/scoring/types.js +40 -0
  59. package/dist/cloudflare/apps/design-system-dashboard/server.js +159 -0
  60. package/dist/cloudflare/apps/token-browser/server.js +136 -0
  61. package/dist/cloudflare/browser/base.js +5 -0
  62. package/dist/cloudflare/browser/cloudflare.js +156 -0
  63. package/dist/cloudflare/browser-manager.js +157 -0
  64. package/dist/cloudflare/core/cloud-websocket-connector.js +267 -0
  65. package/dist/cloudflare/core/cloud-websocket-relay.js +199 -0
  66. package/dist/cloudflare/core/comment-tools.js +292 -0
  67. package/dist/cloudflare/core/config.js +161 -0
  68. package/dist/cloudflare/core/console-monitor.js +427 -0
  69. package/dist/cloudflare/core/design-code-tools.js +2504 -0
  70. package/dist/cloudflare/core/design-system-manifest.js +260 -0
  71. package/dist/cloudflare/core/design-system-tools.js +863 -0
  72. package/dist/cloudflare/core/enrichment/enrichment-service.js +272 -0
  73. package/dist/cloudflare/core/enrichment/index.js +7 -0
  74. package/dist/cloudflare/core/enrichment/relationship-mapper.js +351 -0
  75. package/dist/cloudflare/core/enrichment/style-resolver.js +326 -0
  76. package/dist/cloudflare/core/figma-api.js +409 -0
  77. package/dist/cloudflare/core/figma-connector.js +7 -0
  78. package/dist/cloudflare/core/figma-desktop-connector.js +1184 -0
  79. package/dist/cloudflare/core/figma-reconstruction-spec.js +402 -0
  80. package/dist/cloudflare/core/figma-style-extractor.js +311 -0
  81. package/dist/cloudflare/core/figma-tools.js +2947 -0
  82. package/dist/cloudflare/core/logger.js +53 -0
  83. package/dist/cloudflare/core/port-discovery.js +282 -0
  84. package/dist/cloudflare/core/snippet-injector.js +96 -0
  85. package/dist/cloudflare/core/types/design-code.js +4 -0
  86. package/dist/cloudflare/core/types/enriched.js +5 -0
  87. package/dist/cloudflare/core/types/index.js +4 -0
  88. package/dist/cloudflare/core/websocket-connector.js +256 -0
  89. package/dist/cloudflare/core/websocket-server.js +646 -0
  90. package/dist/cloudflare/core/write-tools.js +2091 -0
  91. package/dist/cloudflare/index.js +2899 -0
  92. package/dist/cloudflare/test-browser.js +88 -0
  93. package/dist/core/comment-tools.d.ts +11 -0
  94. package/dist/core/comment-tools.d.ts.map +1 -0
  95. package/dist/core/comment-tools.js +293 -0
  96. package/dist/core/comment-tools.js.map +1 -0
  97. package/dist/core/config.d.ts +17 -0
  98. package/dist/core/config.d.ts.map +1 -0
  99. package/dist/core/config.js +162 -0
  100. package/dist/core/config.js.map +1 -0
  101. package/dist/core/console-monitor.d.ts +82 -0
  102. package/dist/core/console-monitor.d.ts.map +1 -0
  103. package/dist/core/console-monitor.js +428 -0
  104. package/dist/core/console-monitor.js.map +1 -0
  105. package/dist/core/design-code-tools.d.ts +127 -0
  106. package/dist/core/design-code-tools.d.ts.map +1 -0
  107. package/dist/core/design-code-tools.js +2505 -0
  108. package/dist/core/design-code-tools.js.map +1 -0
  109. package/dist/core/design-system-manifest.d.ts +272 -0
  110. package/dist/core/design-system-manifest.d.ts.map +1 -0
  111. package/dist/core/design-system-manifest.js +261 -0
  112. package/dist/core/design-system-manifest.js.map +1 -0
  113. package/dist/core/design-system-tools.d.ts +17 -0
  114. package/dist/core/design-system-tools.d.ts.map +1 -0
  115. package/dist/core/design-system-tools.js +864 -0
  116. package/dist/core/design-system-tools.js.map +1 -0
  117. package/dist/core/enrichment/enrichment-service.d.ts +52 -0
  118. package/dist/core/enrichment/enrichment-service.d.ts.map +1 -0
  119. package/dist/core/enrichment/enrichment-service.js +273 -0
  120. package/dist/core/enrichment/enrichment-service.js.map +1 -0
  121. package/dist/core/enrichment/index.d.ts +8 -0
  122. package/dist/core/enrichment/index.d.ts.map +1 -0
  123. package/dist/core/enrichment/index.js +8 -0
  124. package/dist/core/enrichment/index.js.map +1 -0
  125. package/dist/core/enrichment/relationship-mapper.d.ts +106 -0
  126. package/dist/core/enrichment/relationship-mapper.d.ts.map +1 -0
  127. package/dist/core/enrichment/relationship-mapper.js +352 -0
  128. package/dist/core/enrichment/relationship-mapper.js.map +1 -0
  129. package/dist/core/enrichment/style-resolver.d.ts +80 -0
  130. package/dist/core/enrichment/style-resolver.d.ts.map +1 -0
  131. package/dist/core/enrichment/style-resolver.js +327 -0
  132. package/dist/core/enrichment/style-resolver.js.map +1 -0
  133. package/dist/core/figma-api.d.ts +201 -0
  134. package/dist/core/figma-api.d.ts.map +1 -0
  135. package/dist/core/figma-api.js +410 -0
  136. package/dist/core/figma-api.js.map +1 -0
  137. package/dist/core/figma-connector.d.ts +48 -0
  138. package/dist/core/figma-connector.d.ts.map +1 -0
  139. package/dist/core/figma-connector.js +8 -0
  140. package/dist/core/figma-connector.js.map +1 -0
  141. package/dist/core/figma-desktop-connector.d.ts +265 -0
  142. package/dist/core/figma-desktop-connector.d.ts.map +1 -0
  143. package/dist/core/figma-desktop-connector.js +1184 -0
  144. package/dist/core/figma-desktop-connector.js.map +1 -0
  145. package/dist/core/figma-reconstruction-spec.d.ts +166 -0
  146. package/dist/core/figma-reconstruction-spec.d.ts.map +1 -0
  147. package/dist/core/figma-reconstruction-spec.js +403 -0
  148. package/dist/core/figma-reconstruction-spec.js.map +1 -0
  149. package/dist/core/figma-style-extractor.d.ts +76 -0
  150. package/dist/core/figma-style-extractor.d.ts.map +1 -0
  151. package/dist/core/figma-style-extractor.js +312 -0
  152. package/dist/core/figma-style-extractor.js.map +1 -0
  153. package/dist/core/figma-tools.d.ts +23 -0
  154. package/dist/core/figma-tools.d.ts.map +1 -0
  155. package/dist/core/figma-tools.js +2948 -0
  156. package/dist/core/figma-tools.js.map +1 -0
  157. package/dist/core/logger.d.ts +22 -0
  158. package/dist/core/logger.d.ts.map +1 -0
  159. package/dist/core/logger.js +54 -0
  160. package/dist/core/logger.js.map +1 -0
  161. package/dist/core/port-discovery.d.ts +110 -0
  162. package/dist/core/port-discovery.d.ts.map +1 -0
  163. package/dist/core/port-discovery.js +283 -0
  164. package/dist/core/port-discovery.js.map +1 -0
  165. package/dist/core/snippet-injector.d.ts +24 -0
  166. package/dist/core/snippet-injector.d.ts.map +1 -0
  167. package/dist/core/snippet-injector.js +97 -0
  168. package/dist/core/snippet-injector.js.map +1 -0
  169. package/dist/core/types/design-code.d.ts +262 -0
  170. package/dist/core/types/design-code.d.ts.map +1 -0
  171. package/dist/core/types/design-code.js +5 -0
  172. package/dist/core/types/design-code.js.map +1 -0
  173. package/dist/core/types/enriched.d.ts +213 -0
  174. package/dist/core/types/enriched.d.ts.map +1 -0
  175. package/dist/core/types/enriched.js +6 -0
  176. package/dist/core/types/enriched.js.map +1 -0
  177. package/dist/core/types/index.d.ts +112 -0
  178. package/dist/core/types/index.d.ts.map +1 -0
  179. package/dist/core/types/index.js +5 -0
  180. package/dist/core/types/index.js.map +1 -0
  181. package/dist/core/websocket-connector.d.ts +55 -0
  182. package/dist/core/websocket-connector.d.ts.map +1 -0
  183. package/dist/core/websocket-connector.js +257 -0
  184. package/dist/core/websocket-connector.js.map +1 -0
  185. package/dist/core/websocket-server.d.ts +191 -0
  186. package/dist/core/websocket-server.d.ts.map +1 -0
  187. package/dist/core/websocket-server.js +647 -0
  188. package/dist/core/websocket-server.js.map +1 -0
  189. package/dist/core/write-tools.d.ts +7 -0
  190. package/dist/core/write-tools.d.ts.map +1 -0
  191. package/dist/core/write-tools.js +2092 -0
  192. package/dist/core/write-tools.js.map +1 -0
  193. package/dist/local.d.ts +84 -0
  194. package/dist/local.d.ts.map +1 -0
  195. package/dist/local.js +5039 -0
  196. package/dist/local.js.map +1 -0
  197. package/figma-desktop-bridge/README.md +313 -0
  198. package/figma-desktop-bridge/code.js +2818 -0
  199. package/figma-desktop-bridge/manifest.json +67 -0
  200. package/figma-desktop-bridge/ui.html +1236 -0
  201. 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
+ }