@atezer/figma-mcp-bridge 1.4.0 → 1.4.1

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 (34) hide show
  1. package/CHANGELOG.md +9 -0
  2. package/package.json +10 -2
  3. package/dist/browser/base.d.ts +0 -50
  4. package/dist/browser/base.d.ts.map +0 -1
  5. package/dist/browser/base.js +0 -6
  6. package/dist/browser/base.js.map +0 -1
  7. package/dist/browser/local.d.ts +0 -81
  8. package/dist/browser/local.d.ts.map +0 -1
  9. package/dist/browser/local.js +0 -283
  10. package/dist/browser/local.js.map +0 -1
  11. package/dist/cloudflare/browser/base.js +0 -5
  12. package/dist/cloudflare/browser/cloudflare.js +0 -156
  13. package/dist/cloudflare/browser-manager.js +0 -157
  14. package/dist/cloudflare/core/audit-log.js +0 -62
  15. package/dist/cloudflare/core/config.js +0 -163
  16. package/dist/cloudflare/core/console-monitor.js +0 -427
  17. package/dist/cloudflare/core/design-system-manifest.js +0 -260
  18. package/dist/cloudflare/core/enrichment/enrichment-service.js +0 -272
  19. package/dist/cloudflare/core/enrichment/index.js +0 -7
  20. package/dist/cloudflare/core/enrichment/relationship-mapper.js +0 -351
  21. package/dist/cloudflare/core/enrichment/style-resolver.js +0 -326
  22. package/dist/cloudflare/core/figma-api.js +0 -273
  23. package/dist/cloudflare/core/figma-desktop-connector.js +0 -1041
  24. package/dist/cloudflare/core/figma-reconstruction-spec.js +0 -402
  25. package/dist/cloudflare/core/figma-tools.js +0 -2919
  26. package/dist/cloudflare/core/figma-url.js +0 -48
  27. package/dist/cloudflare/core/logger.js +0 -53
  28. package/dist/cloudflare/core/plugin-bridge-connector.js +0 -197
  29. package/dist/cloudflare/core/plugin-bridge-server.js +0 -375
  30. package/dist/cloudflare/core/snippet-injector.js +0 -96
  31. package/dist/cloudflare/core/types/enriched.js +0 -5
  32. package/dist/cloudflare/core/types/index.js +0 -4
  33. package/dist/cloudflare/index.js +0 -1061
  34. package/dist/cloudflare/test-browser.js +0 -88
@@ -1,326 +0,0 @@
1
- /**
2
- * Style Value Resolver
3
- * Resolves style values from variable references and provides formatted outputs
4
- */
5
- export class StyleValueResolver {
6
- constructor(logger) {
7
- this.cache = new Map();
8
- this.variableCache = new Map();
9
- this.logger = logger;
10
- }
11
- /**
12
- * Resolve a style's value, handling variable references
13
- */
14
- async resolveStyleValue(style, variables, maxDepth = 10) {
15
- const cacheKey = `style:${style.key || style.node_id}`;
16
- // Check cache
17
- if (this.cache.has(cacheKey)) {
18
- return this.cache.get(cacheKey);
19
- }
20
- try {
21
- // Get the actual style node data
22
- const styleData = await this.getStyleData(style);
23
- if (!styleData) {
24
- return { value: null };
25
- }
26
- // Check if this style uses a variable
27
- const variableRef = this.findVariableReference(styleData);
28
- if (variableRef && variables.has(variableRef.id)) {
29
- // Resolve the variable value
30
- const variable = variables.get(variableRef.id);
31
- const resolvedValue = await this.resolveVariableValue(variable, variables, maxDepth);
32
- const result = {
33
- value: resolvedValue,
34
- variableRef: {
35
- id: variableRef.id,
36
- name: variable.name,
37
- collection: variable.variableCollectionId,
38
- resolvedType: variable.resolvedType,
39
- },
40
- };
41
- this.cache.set(cacheKey, result);
42
- return result;
43
- }
44
- // No variable reference, return direct value
45
- const directValue = this.extractDirectValue(styleData, style.style_type);
46
- const result = { value: directValue };
47
- this.cache.set(cacheKey, result);
48
- return result;
49
- }
50
- catch (error) {
51
- this.logger.error({
52
- error,
53
- style: style.name,
54
- }, "Error resolving style value");
55
- return { value: null };
56
- }
57
- }
58
- /**
59
- * Resolve a variable's value, handling alias chains
60
- */
61
- async resolveVariableValue(variable, allVariables, maxDepth = 10, currentDepth = 0) {
62
- if (currentDepth >= maxDepth) {
63
- this.logger.warn({
64
- variable: variable.name,
65
- }, "Max resolution depth reached");
66
- return null;
67
- }
68
- const cacheKey = `var:${variable.id}`;
69
- if (this.variableCache.has(cacheKey)) {
70
- return this.variableCache.get(cacheKey);
71
- }
72
- try {
73
- // Get the value for the default mode (or first available mode)
74
- const modes = Object.keys(variable.valuesByMode || {});
75
- if (modes.length === 0) {
76
- return null;
77
- }
78
- const defaultMode = modes[0]; // TODO: Support mode selection
79
- const value = variable.valuesByMode[defaultMode];
80
- // Check if this is an alias (reference to another variable)
81
- if (typeof value === "object" && value.type === "VARIABLE_ALIAS") {
82
- const targetVariable = allVariables.get(value.id);
83
- if (!targetVariable) {
84
- this.logger.warn({
85
- source: variable.name,
86
- targetId: value.id,
87
- }, "Variable alias target not found");
88
- return null;
89
- }
90
- // Recursively resolve the alias
91
- const resolvedValue = await this.resolveVariableValue(targetVariable, allVariables, maxDepth, currentDepth + 1);
92
- this.variableCache.set(cacheKey, resolvedValue);
93
- return resolvedValue;
94
- }
95
- // Direct value - format based on type
96
- const formattedValue = this.formatVariableValue(value, variable.resolvedType);
97
- this.variableCache.set(cacheKey, formattedValue);
98
- return formattedValue;
99
- }
100
- catch (error) {
101
- this.logger.error({
102
- error,
103
- variable: variable.name,
104
- }, "Error resolving variable value");
105
- return null;
106
- }
107
- }
108
- /**
109
- * Format a variable value based on its type
110
- */
111
- formatVariableValue(value, type) {
112
- if (!value)
113
- return null;
114
- switch (type) {
115
- case "COLOR":
116
- return this.formatColor(value);
117
- case "FLOAT":
118
- case "NUMBER":
119
- return value;
120
- case "STRING":
121
- return value;
122
- case "BOOLEAN":
123
- return Boolean(value);
124
- default:
125
- return value;
126
- }
127
- }
128
- /**
129
- * Format a color value to hex string
130
- */
131
- formatColor(color) {
132
- if (typeof color === "string") {
133
- return color;
134
- }
135
- if (color.r !== undefined && color.g !== undefined && color.b !== undefined) {
136
- const r = Math.round(color.r * 255);
137
- const g = Math.round(color.g * 255);
138
- const b = Math.round(color.b * 255);
139
- return `#${r.toString(16).padStart(2, "0")}${g.toString(16).padStart(2, "0")}${b.toString(16).padStart(2, "0")}`.toUpperCase();
140
- }
141
- return null;
142
- }
143
- /**
144
- * Generate export formats for a resolved value
145
- */
146
- generateExportFormats(name, value, type, formats = ["css", "sass", "tailwind", "typescript", "json"]) {
147
- const result = {};
148
- // Sanitize name for different formats
149
- const cssName = this.toCSSVariableName(name);
150
- const sassName = this.toSassVariableName(name);
151
- const tailwindName = this.toTailwindClassName(name);
152
- const tsName = this.toTypeScriptPath(name);
153
- const jsonPath = this.toJSONPath(name);
154
- for (const format of formats) {
155
- switch (format) {
156
- case "css":
157
- result.css = `var(${cssName})`;
158
- break;
159
- case "sass":
160
- result.sass = sassName;
161
- break;
162
- case "tailwind":
163
- result.tailwind = tailwindName;
164
- break;
165
- case "typescript":
166
- result.typescript = tsName;
167
- break;
168
- case "json":
169
- result.json = jsonPath;
170
- break;
171
- }
172
- }
173
- return result;
174
- }
175
- /**
176
- * Convert token name to CSS variable format
177
- * Example: "color/background/primary-default" -> "--color-background-primary-default"
178
- */
179
- toCSSVariableName(name) {
180
- return `--${name.replace(/\//g, "-").toLowerCase()}`;
181
- }
182
- /**
183
- * Convert token name to Sass variable format
184
- * Example: "color/background/primary-default" -> "$color-background-primary-default"
185
- */
186
- toSassVariableName(name) {
187
- return `$${name.replace(/\//g, "-").toLowerCase()}`;
188
- }
189
- /**
190
- * Convert token name to Tailwind class format
191
- * Example: "color/background/primary-default" -> "bg-primary"
192
- */
193
- toTailwindClassName(name) {
194
- const parts = name.split("/");
195
- // Try to infer Tailwind utility class
196
- if (parts[0] === "color") {
197
- if (parts[1] === "background") {
198
- return `bg-${parts[parts.length - 1].toLowerCase()}`;
199
- }
200
- if (parts[1] === "text") {
201
- return `text-${parts[parts.length - 1].toLowerCase()}`;
202
- }
203
- if (parts[1] === "border") {
204
- return `border-${parts[parts.length - 1].toLowerCase()}`;
205
- }
206
- }
207
- if (parts[0] === "spacing") {
208
- return `space-${parts[parts.length - 1].toLowerCase()}`;
209
- }
210
- // Fallback: use last part
211
- return parts[parts.length - 1].toLowerCase();
212
- }
213
- /**
214
- * Convert token name to TypeScript path format
215
- * Example: "color/background/primary-default" -> "tokens.color.background.primaryDefault"
216
- */
217
- toTypeScriptPath(name) {
218
- const parts = name.split("/");
219
- const camelCaseParts = parts.map((part, index) => {
220
- if (index === 0)
221
- return part.toLowerCase();
222
- return part
223
- .split("-")
224
- .map((word, i) => i === 0
225
- ? word.toLowerCase()
226
- : word.charAt(0).toUpperCase() + word.slice(1).toLowerCase())
227
- .join("");
228
- });
229
- return `tokens.${camelCaseParts.join(".")}`;
230
- }
231
- /**
232
- * Convert token name to nested JSON path
233
- * Example: "color/background/primary-default" -> { color: { background: { primaryDefault: value } } }
234
- */
235
- toJSONPath(name) {
236
- const parts = name.split("/");
237
- const result = {};
238
- let current = result;
239
- for (let i = 0; i < parts.length; i++) {
240
- const key = parts[i]
241
- .split("-")
242
- .map((word, j) => j === 0
243
- ? word.toLowerCase()
244
- : word.charAt(0).toUpperCase() + word.slice(1).toLowerCase())
245
- .join("");
246
- if (i === parts.length - 1) {
247
- current[key] = "[VALUE]"; // Placeholder
248
- }
249
- else {
250
- current[key] = {};
251
- current = current[key];
252
- }
253
- }
254
- return result;
255
- }
256
- /**
257
- * Get style data from Figma API
258
- * This would be called via the Figma API client
259
- */
260
- async getStyleData(style) {
261
- // TODO: Implement actual Figma API call
262
- // For now, return the style object itself
263
- return style;
264
- }
265
- /**
266
- * Find variable reference in style data
267
- */
268
- findVariableReference(styleData) {
269
- // Check for boundVariables (new variables API)
270
- if (styleData.boundVariables) {
271
- // Check common properties that can be bound to variables
272
- const props = ["fills", "strokes", "effects", "text"];
273
- for (const prop of props) {
274
- if (styleData.boundVariables[prop]) {
275
- const binding = styleData.boundVariables[prop];
276
- if (Array.isArray(binding) && binding.length > 0) {
277
- return { id: binding[0].id };
278
- }
279
- if (binding.id) {
280
- return { id: binding.id };
281
- }
282
- }
283
- }
284
- }
285
- return null;
286
- }
287
- /**
288
- * Extract direct value from style (no variable)
289
- */
290
- extractDirectValue(styleData, styleType) {
291
- switch (styleType) {
292
- case "FILL":
293
- if (styleData.fills && styleData.fills.length > 0) {
294
- const fill = styleData.fills[0];
295
- if (fill.type === "SOLID") {
296
- return this.formatColor(fill.color);
297
- }
298
- }
299
- return null;
300
- case "TEXT":
301
- if (styleData.fontFamily) {
302
- return {
303
- fontFamily: styleData.fontFamily,
304
- fontSize: styleData.fontSize,
305
- fontWeight: styleData.fontWeight,
306
- lineHeight: styleData.lineHeight,
307
- };
308
- }
309
- return null;
310
- case "EFFECT":
311
- if (styleData.effects && styleData.effects.length > 0) {
312
- return styleData.effects;
313
- }
314
- return null;
315
- default:
316
- return null;
317
- }
318
- }
319
- /**
320
- * Clear the cache
321
- */
322
- clearCache() {
323
- this.cache.clear();
324
- this.variableCache.clear();
325
- }
326
- }
@@ -1,273 +0,0 @@
1
- /**
2
- * Figma REST API Client
3
- * Handles HTTP calls to Figma's REST API for file data, variables, components, and styles
4
- */
5
- import { createChildLogger } from './logger.js';
6
- const logger = createChildLogger({ component: 'figma-api' });
7
- const FIGMA_API_BASE = 'https://api.figma.com/v1';
8
- /**
9
- * Extract file key from Figma URL
10
- * @example https://www.figma.com/design/abc123/My-File -> abc123
11
- */
12
- export function extractFileKey(url) {
13
- try {
14
- const urlObj = new URL(url);
15
- // Match patterns like /design/FILE_KEY or /file/FILE_KEY
16
- const match = urlObj.pathname.match(/\/(design|file)\/([a-zA-Z0-9]+)/);
17
- return match ? match[2] : null;
18
- }
19
- catch (error) {
20
- logger.error({ error, url }, 'Failed to extract file key from URL');
21
- return null;
22
- }
23
- }
24
- /**
25
- * Figma API Client
26
- * Makes authenticated requests to Figma REST API
27
- */
28
- export class FigmaAPI {
29
- constructor(config) {
30
- this.accessToken = config.accessToken;
31
- }
32
- /**
33
- * Make authenticated request to Figma API
34
- */
35
- async request(endpoint, options = {}) {
36
- const url = `${FIGMA_API_BASE}${endpoint}`;
37
- // Detect token type and use appropriate authentication header
38
- // OAuth tokens start with 'figu_' and require Authorization: Bearer header
39
- // Personal Access Tokens use X-Figma-Token header
40
- const isOAuthToken = this.accessToken.startsWith('figu_');
41
- // Debug logging to verify token is being used
42
- const tokenPreview = this.accessToken ? `${this.accessToken.substring(0, 10)}...` : 'NO TOKEN';
43
- logger.info({
44
- url,
45
- tokenPreview,
46
- hasToken: !!this.accessToken,
47
- tokenLength: this.accessToken?.length,
48
- isOAuthToken,
49
- authMethod: isOAuthToken ? 'Bearer' : 'X-Figma-Token'
50
- }, 'Making Figma API request with token');
51
- const headers = {
52
- 'Content-Type': 'application/json',
53
- ...(options.headers || {}),
54
- };
55
- // Add authentication header based on token type
56
- if (isOAuthToken) {
57
- headers['Authorization'] = `Bearer ${this.accessToken}`;
58
- }
59
- else {
60
- headers['X-Figma-Token'] = this.accessToken;
61
- }
62
- const response = await fetch(url, {
63
- ...options,
64
- headers,
65
- });
66
- if (!response.ok) {
67
- const errorText = await response.text();
68
- logger.error({ status: response.status, statusText: response.statusText, body: errorText }, 'Figma API request failed');
69
- throw new Error(`Figma API error (${response.status}): ${errorText}`);
70
- }
71
- const data = await response.json();
72
- return data;
73
- }
74
- /**
75
- * GET /v1/files/:file_key
76
- * Get full file data including document tree, components, and styles
77
- */
78
- async getFile(fileKey, options) {
79
- let endpoint = `/files/${fileKey}`;
80
- const params = new URLSearchParams();
81
- if (options?.version)
82
- params.append('version', options.version);
83
- if (options?.ids)
84
- params.append('ids', options.ids.join(','));
85
- if (options?.depth !== undefined)
86
- params.append('depth', options.depth.toString());
87
- if (options?.geometry)
88
- params.append('geometry', options.geometry);
89
- if (options?.plugin_data)
90
- params.append('plugin_data', options.plugin_data);
91
- if (options?.branch_data)
92
- params.append('branch_data', 'true');
93
- if (params.toString()) {
94
- endpoint += `?${params.toString()}`;
95
- }
96
- return this.request(endpoint);
97
- }
98
- /**
99
- * GET /v1/files/:file_key/variables/local
100
- * Get local variables (design tokens) from a file
101
- */
102
- async getLocalVariables(fileKey) {
103
- const response = await this.request(`/files/${fileKey}/variables/local`);
104
- // Figma API returns {status, error, meta: {variableCollections, variables}}
105
- // Extract meta to match expected format
106
- return response.meta || response;
107
- }
108
- /**
109
- * GET /v1/files/:file_key/variables/published
110
- * Get published variables from a file
111
- */
112
- async getPublishedVariables(fileKey) {
113
- const response = await this.request(`/files/${fileKey}/variables/published`);
114
- // Figma API returns {status, error, meta: {variableCollections, variables}}
115
- // Extract meta to match expected format
116
- return response.meta || response;
117
- }
118
- /**
119
- * GET /v1/files/:file_key/nodes
120
- * Get specific nodes by ID
121
- */
122
- async getNodes(fileKey, nodeIds, options) {
123
- let endpoint = `/files/${fileKey}/nodes`;
124
- const params = new URLSearchParams();
125
- params.append('ids', nodeIds.join(','));
126
- if (options?.version)
127
- params.append('version', options.version);
128
- if (options?.depth !== undefined)
129
- params.append('depth', options.depth.toString());
130
- if (options?.geometry)
131
- params.append('geometry', options.geometry);
132
- if (options?.plugin_data)
133
- params.append('plugin_data', options.plugin_data);
134
- endpoint += `?${params.toString()}`;
135
- return this.request(endpoint);
136
- }
137
- /**
138
- * GET /v1/files/:file_key/styles
139
- * Get styles from a file
140
- */
141
- async getStyles(fileKey) {
142
- return this.request(`/files/${fileKey}/styles`);
143
- }
144
- /**
145
- * GET /v1/files/:file_key/components
146
- * Get components from a file
147
- */
148
- async getComponents(fileKey) {
149
- return this.request(`/files/${fileKey}/components`);
150
- }
151
- /**
152
- * GET /v1/files/:file_key/component_sets
153
- * Get component sets (variants) from a file
154
- */
155
- async getComponentSets(fileKey) {
156
- return this.request(`/files/${fileKey}/component_sets`);
157
- }
158
- /**
159
- * GET /v1/images/:file_key
160
- * Renders images for specified nodes
161
- * @param fileKey - The file key
162
- * @param nodeIds - Node IDs to render (single string or array)
163
- * @param options - Rendering options
164
- * @returns Map of node IDs to image URLs (URLs expire after 30 days)
165
- */
166
- async getImages(fileKey, nodeIds, options) {
167
- const params = new URLSearchParams();
168
- // Handle single or multiple node IDs
169
- const ids = Array.isArray(nodeIds) ? nodeIds.join(',') : nodeIds;
170
- params.append('ids', ids);
171
- // Add optional parameters
172
- if (options?.scale !== undefined)
173
- params.append('scale', options.scale.toString());
174
- if (options?.format)
175
- params.append('format', options.format);
176
- if (options?.svg_outline_text !== undefined)
177
- params.append('svg_outline_text', options.svg_outline_text.toString());
178
- if (options?.svg_include_id !== undefined)
179
- params.append('svg_include_id', options.svg_include_id.toString());
180
- if (options?.svg_include_node_id !== undefined)
181
- params.append('svg_include_node_id', options.svg_include_node_id.toString());
182
- if (options?.svg_simplify_stroke !== undefined)
183
- params.append('svg_simplify_stroke', options.svg_simplify_stroke.toString());
184
- if (options?.contents_only !== undefined)
185
- params.append('contents_only', options.contents_only.toString());
186
- const endpoint = `/images/${fileKey}?${params.toString()}`;
187
- logger.info({ fileKey, ids, options }, 'Rendering images');
188
- return this.request(endpoint);
189
- }
190
- /**
191
- * Helper: Get all design tokens (variables) with formatted output
192
- */
193
- async getAllVariables(fileKey) {
194
- // Don't catch errors - let them bubble up so proper error messages are shown
195
- const [local, published] = await Promise.all([
196
- this.getLocalVariables(fileKey),
197
- this.getPublishedVariables(fileKey).catch(() => ({ variables: {} })), // Published can fail gracefully
198
- ]);
199
- return { local, published };
200
- }
201
- /**
202
- * Helper: Get component metadata with properties
203
- */
204
- async getComponentData(fileKey, nodeId) {
205
- const response = await this.getNodes(fileKey, [nodeId], { depth: 2 });
206
- return response.nodes?.[nodeId];
207
- }
208
- /**
209
- * Helper: Search for components by name
210
- */
211
- async searchComponents(fileKey, searchTerm) {
212
- const { meta } = await this.getComponents(fileKey);
213
- const components = meta?.components || [];
214
- return components.filter((comp) => comp.name?.toLowerCase().includes(searchTerm.toLowerCase()));
215
- }
216
- }
217
- /**
218
- * Helper function to format variables for display
219
- */
220
- export function formatVariables(variablesData) {
221
- const collections = Object.entries(variablesData.variableCollections || {}).map(([id, collection]) => ({
222
- id,
223
- name: collection.name,
224
- key: collection.key,
225
- modes: collection.modes,
226
- variableIds: collection.variableIds,
227
- }));
228
- const variables = Object.entries(variablesData.variables || {}).map(([id, variable]) => ({
229
- id,
230
- name: variable.name,
231
- key: variable.key,
232
- resolvedType: variable.resolvedType,
233
- valuesByMode: variable.valuesByMode,
234
- variableCollectionId: variable.variableCollectionId,
235
- scopes: variable.scopes,
236
- description: variable.description,
237
- }));
238
- const variablesByType = variables.reduce((acc, v) => {
239
- acc[v.resolvedType] = (acc[v.resolvedType] || 0) + 1;
240
- return acc;
241
- }, {});
242
- return {
243
- collections,
244
- variables,
245
- summary: {
246
- totalCollections: collections.length,
247
- totalVariables: variables.length,
248
- variablesByType,
249
- },
250
- };
251
- }
252
- /**
253
- * Helper function to format component data for display
254
- */
255
- export function formatComponentData(componentNode) {
256
- return {
257
- id: componentNode.id,
258
- name: componentNode.name,
259
- type: componentNode.type,
260
- description: componentNode.description,
261
- descriptionMarkdown: componentNode.descriptionMarkdown,
262
- properties: componentNode.componentPropertyDefinitions,
263
- children: componentNode.children?.map((child) => ({
264
- id: child.id,
265
- name: child.name,
266
- type: child.type,
267
- })),
268
- bounds: componentNode.absoluteBoundingBox,
269
- fills: componentNode.fills,
270
- strokes: componentNode.strokes,
271
- effects: componentNode.effects,
272
- };
273
- }