@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,156 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Cloudflare Browser Manager
|
|
3
|
+
* Manages Puppeteer browser instance lifecycle for Cloudflare Browser Rendering API
|
|
4
|
+
*/
|
|
5
|
+
import puppeteer from '@cloudflare/puppeteer';
|
|
6
|
+
import { createChildLogger } from '../core/logger.js';
|
|
7
|
+
const logger = createChildLogger({ component: 'cloudflare-browser' });
|
|
8
|
+
/**
|
|
9
|
+
* Cloudflare Browser Manager
|
|
10
|
+
* Implements IBrowserManager for Cloudflare Browser Rendering API
|
|
11
|
+
*/
|
|
12
|
+
export class CloudflareBrowserManager {
|
|
13
|
+
constructor(env, config) {
|
|
14
|
+
this.browser = null;
|
|
15
|
+
this.page = null;
|
|
16
|
+
this.env = env;
|
|
17
|
+
this.config = config;
|
|
18
|
+
}
|
|
19
|
+
/**
|
|
20
|
+
* Launch browser instance via Cloudflare Browser Rendering API
|
|
21
|
+
*/
|
|
22
|
+
async launch() {
|
|
23
|
+
if (this.browser) {
|
|
24
|
+
logger.info('Browser already running, reusing instance');
|
|
25
|
+
return;
|
|
26
|
+
}
|
|
27
|
+
logger.info('Launching browser with Cloudflare Browser Rendering API');
|
|
28
|
+
try {
|
|
29
|
+
this.browser = await puppeteer.launch(this.env.BROWSER, {
|
|
30
|
+
keep_alive: 600000, // Keep alive for 10 minutes
|
|
31
|
+
});
|
|
32
|
+
logger.info('Browser launched successfully');
|
|
33
|
+
}
|
|
34
|
+
catch (error) {
|
|
35
|
+
logger.error({ error }, 'Failed to launch browser');
|
|
36
|
+
throw new Error(`Browser launch failed: ${error}`);
|
|
37
|
+
}
|
|
38
|
+
}
|
|
39
|
+
/**
|
|
40
|
+
* Get or create a page instance
|
|
41
|
+
*/
|
|
42
|
+
async getPage() {
|
|
43
|
+
if (!this.browser) {
|
|
44
|
+
await this.launch();
|
|
45
|
+
}
|
|
46
|
+
if (this.page && !this.page.isClosed()) {
|
|
47
|
+
return this.page;
|
|
48
|
+
}
|
|
49
|
+
logger.info('Creating new browser page');
|
|
50
|
+
this.page = await this.browser.newPage();
|
|
51
|
+
// Set viewport size
|
|
52
|
+
await this.page.setViewport({
|
|
53
|
+
width: 1920,
|
|
54
|
+
height: 1080,
|
|
55
|
+
deviceScaleFactor: 1,
|
|
56
|
+
});
|
|
57
|
+
logger.info('Browser page created');
|
|
58
|
+
return this.page;
|
|
59
|
+
}
|
|
60
|
+
/**
|
|
61
|
+
* Navigate to Figma URL
|
|
62
|
+
*/
|
|
63
|
+
async navigateToFigma(figmaUrl) {
|
|
64
|
+
const page = await this.getPage();
|
|
65
|
+
// Default to Figma homepage if no URL provided
|
|
66
|
+
const url = figmaUrl || 'https://www.figma.com';
|
|
67
|
+
logger.info({ url }, 'Navigating to Figma');
|
|
68
|
+
try {
|
|
69
|
+
await page.goto(url, {
|
|
70
|
+
waitUntil: 'networkidle2',
|
|
71
|
+
timeout: 30000,
|
|
72
|
+
});
|
|
73
|
+
logger.info({ url }, 'Navigation successful');
|
|
74
|
+
return { page, action: 'navigated', url };
|
|
75
|
+
}
|
|
76
|
+
catch (error) {
|
|
77
|
+
logger.error({ error, url }, 'Navigation failed');
|
|
78
|
+
throw new Error(`Failed to navigate to ${url}: ${error}`);
|
|
79
|
+
}
|
|
80
|
+
}
|
|
81
|
+
/**
|
|
82
|
+
* Reload current page
|
|
83
|
+
*/
|
|
84
|
+
async reload(hardReload = false) {
|
|
85
|
+
if (!this.page || this.page.isClosed()) {
|
|
86
|
+
throw new Error('No active page to reload');
|
|
87
|
+
}
|
|
88
|
+
logger.info({ hardReload }, 'Reloading page');
|
|
89
|
+
try {
|
|
90
|
+
await this.page.reload({
|
|
91
|
+
waitUntil: 'networkidle2',
|
|
92
|
+
timeout: 30000,
|
|
93
|
+
});
|
|
94
|
+
logger.info('Page reloaded successfully');
|
|
95
|
+
}
|
|
96
|
+
catch (error) {
|
|
97
|
+
logger.error({ error }, 'Page reload failed');
|
|
98
|
+
throw new Error(`Page reload failed: ${error}`);
|
|
99
|
+
}
|
|
100
|
+
}
|
|
101
|
+
/**
|
|
102
|
+
* Execute JavaScript in page context
|
|
103
|
+
*/
|
|
104
|
+
async evaluate(fn) {
|
|
105
|
+
const page = await this.getPage();
|
|
106
|
+
return page.evaluate(fn);
|
|
107
|
+
}
|
|
108
|
+
// Screenshot functionality removed - use Figma REST API's getImages() instead
|
|
109
|
+
// See: figma_take_screenshot and figma_get_component_image tools
|
|
110
|
+
/**
|
|
111
|
+
* Check if browser is running
|
|
112
|
+
*/
|
|
113
|
+
isRunning() {
|
|
114
|
+
return this.browser !== null && this.browser.isConnected();
|
|
115
|
+
}
|
|
116
|
+
/**
|
|
117
|
+
* Close browser instance
|
|
118
|
+
*/
|
|
119
|
+
async close() {
|
|
120
|
+
if (!this.browser) {
|
|
121
|
+
return;
|
|
122
|
+
}
|
|
123
|
+
logger.info('Closing browser');
|
|
124
|
+
try {
|
|
125
|
+
await this.browser.close();
|
|
126
|
+
this.browser = null;
|
|
127
|
+
this.page = null;
|
|
128
|
+
logger.info('Browser closed successfully');
|
|
129
|
+
}
|
|
130
|
+
catch (error) {
|
|
131
|
+
logger.error({ error }, 'Failed to close browser');
|
|
132
|
+
throw error;
|
|
133
|
+
}
|
|
134
|
+
}
|
|
135
|
+
/**
|
|
136
|
+
* Get current page URL
|
|
137
|
+
*/
|
|
138
|
+
getCurrentUrl() {
|
|
139
|
+
if (!this.page || this.page.isClosed()) {
|
|
140
|
+
return null;
|
|
141
|
+
}
|
|
142
|
+
return this.page.url();
|
|
143
|
+
}
|
|
144
|
+
/**
|
|
145
|
+
* Wait for navigation
|
|
146
|
+
*/
|
|
147
|
+
async waitForNavigation(timeout = 30000) {
|
|
148
|
+
if (!this.page || this.page.isClosed()) {
|
|
149
|
+
throw new Error('No active page');
|
|
150
|
+
}
|
|
151
|
+
await this.page.waitForNavigation({
|
|
152
|
+
waitUntil: 'networkidle2',
|
|
153
|
+
timeout,
|
|
154
|
+
});
|
|
155
|
+
}
|
|
156
|
+
}
|
|
@@ -0,0 +1,157 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Browser Manager
|
|
3
|
+
* Manages Puppeteer browser instance lifecycle for Cloudflare Browser Rendering API
|
|
4
|
+
*/
|
|
5
|
+
import puppeteer from '@cloudflare/puppeteer';
|
|
6
|
+
import { createChildLogger } from './core/logger.js';
|
|
7
|
+
const logger = createChildLogger({ component: 'browser-manager' });
|
|
8
|
+
/**
|
|
9
|
+
* Browser Manager
|
|
10
|
+
* Handles browser instance creation, page management, and navigation
|
|
11
|
+
*/
|
|
12
|
+
export class BrowserManager {
|
|
13
|
+
constructor(env, config) {
|
|
14
|
+
this.browser = null;
|
|
15
|
+
this.page = null;
|
|
16
|
+
this.env = env;
|
|
17
|
+
this.config = config;
|
|
18
|
+
}
|
|
19
|
+
/**
|
|
20
|
+
* Launch browser instance
|
|
21
|
+
*/
|
|
22
|
+
async launch() {
|
|
23
|
+
if (this.browser) {
|
|
24
|
+
logger.info('Browser already running, reusing instance');
|
|
25
|
+
return this.browser;
|
|
26
|
+
}
|
|
27
|
+
logger.info('Launching browser with Cloudflare Browser Rendering API');
|
|
28
|
+
try {
|
|
29
|
+
this.browser = await puppeteer.launch(this.env.BROWSER, {
|
|
30
|
+
keep_alive: 600000, // Keep alive for 10 minutes
|
|
31
|
+
});
|
|
32
|
+
logger.info('Browser launched successfully');
|
|
33
|
+
return this.browser;
|
|
34
|
+
}
|
|
35
|
+
catch (error) {
|
|
36
|
+
logger.error({ error }, 'Failed to launch browser');
|
|
37
|
+
throw new Error(`Browser launch failed: ${error}`);
|
|
38
|
+
}
|
|
39
|
+
}
|
|
40
|
+
/**
|
|
41
|
+
* Get or create a page instance
|
|
42
|
+
*/
|
|
43
|
+
async getPage() {
|
|
44
|
+
if (!this.browser) {
|
|
45
|
+
await this.launch();
|
|
46
|
+
}
|
|
47
|
+
if (this.page && !this.page.isClosed()) {
|
|
48
|
+
return this.page;
|
|
49
|
+
}
|
|
50
|
+
logger.info('Creating new browser page');
|
|
51
|
+
this.page = await this.browser.newPage();
|
|
52
|
+
// Set viewport size
|
|
53
|
+
await this.page.setViewport({
|
|
54
|
+
width: 1920,
|
|
55
|
+
height: 1080,
|
|
56
|
+
deviceScaleFactor: 1,
|
|
57
|
+
});
|
|
58
|
+
logger.info('Browser page created');
|
|
59
|
+
return this.page;
|
|
60
|
+
}
|
|
61
|
+
/**
|
|
62
|
+
* Navigate to Figma URL
|
|
63
|
+
*/
|
|
64
|
+
async navigateToFigma(figmaUrl) {
|
|
65
|
+
const page = await this.getPage();
|
|
66
|
+
// Default to Figma homepage if no URL provided
|
|
67
|
+
const url = figmaUrl || 'https://www.figma.com';
|
|
68
|
+
logger.info({ url }, 'Navigating to Figma');
|
|
69
|
+
try {
|
|
70
|
+
await page.goto(url, {
|
|
71
|
+
waitUntil: 'networkidle2',
|
|
72
|
+
timeout: 30000,
|
|
73
|
+
});
|
|
74
|
+
logger.info({ url }, 'Navigation successful');
|
|
75
|
+
return { page, action: 'navigated', url };
|
|
76
|
+
}
|
|
77
|
+
catch (error) {
|
|
78
|
+
logger.error({ error, url }, 'Navigation failed');
|
|
79
|
+
throw new Error(`Failed to navigate to ${url}: ${error}`);
|
|
80
|
+
}
|
|
81
|
+
}
|
|
82
|
+
/**
|
|
83
|
+
* Reload current page
|
|
84
|
+
*/
|
|
85
|
+
async reload(hardReload = false) {
|
|
86
|
+
if (!this.page || this.page.isClosed()) {
|
|
87
|
+
throw new Error('No active page to reload');
|
|
88
|
+
}
|
|
89
|
+
logger.info({ hardReload }, 'Reloading page');
|
|
90
|
+
try {
|
|
91
|
+
await this.page.reload({
|
|
92
|
+
waitUntil: 'networkidle2',
|
|
93
|
+
timeout: 30000,
|
|
94
|
+
});
|
|
95
|
+
logger.info('Page reloaded successfully');
|
|
96
|
+
}
|
|
97
|
+
catch (error) {
|
|
98
|
+
logger.error({ error }, 'Page reload failed');
|
|
99
|
+
throw new Error(`Page reload failed: ${error}`);
|
|
100
|
+
}
|
|
101
|
+
}
|
|
102
|
+
/**
|
|
103
|
+
* Execute JavaScript in page context
|
|
104
|
+
*/
|
|
105
|
+
async evaluate(fn) {
|
|
106
|
+
const page = await this.getPage();
|
|
107
|
+
return page.evaluate(fn);
|
|
108
|
+
}
|
|
109
|
+
// Screenshot functionality removed - use Figma REST API's getImages() instead
|
|
110
|
+
// See: figma_take_screenshot and figma_get_component_image tools
|
|
111
|
+
/**
|
|
112
|
+
* Check if browser is running
|
|
113
|
+
*/
|
|
114
|
+
isRunning() {
|
|
115
|
+
return this.browser !== null && this.browser.isConnected();
|
|
116
|
+
}
|
|
117
|
+
/**
|
|
118
|
+
* Close browser instance
|
|
119
|
+
*/
|
|
120
|
+
async close() {
|
|
121
|
+
if (!this.browser) {
|
|
122
|
+
return;
|
|
123
|
+
}
|
|
124
|
+
logger.info('Closing browser');
|
|
125
|
+
try {
|
|
126
|
+
await this.browser.close();
|
|
127
|
+
this.browser = null;
|
|
128
|
+
this.page = null;
|
|
129
|
+
logger.info('Browser closed successfully');
|
|
130
|
+
}
|
|
131
|
+
catch (error) {
|
|
132
|
+
logger.error({ error }, 'Failed to close browser');
|
|
133
|
+
throw error;
|
|
134
|
+
}
|
|
135
|
+
}
|
|
136
|
+
/**
|
|
137
|
+
* Get current page URL
|
|
138
|
+
*/
|
|
139
|
+
getCurrentUrl() {
|
|
140
|
+
if (!this.page || this.page.isClosed()) {
|
|
141
|
+
return null;
|
|
142
|
+
}
|
|
143
|
+
return this.page.url();
|
|
144
|
+
}
|
|
145
|
+
/**
|
|
146
|
+
* Wait for navigation
|
|
147
|
+
*/
|
|
148
|
+
async waitForNavigation(timeout = 30000) {
|
|
149
|
+
if (!this.page || this.page.isClosed()) {
|
|
150
|
+
throw new Error('No active page');
|
|
151
|
+
}
|
|
152
|
+
await this.page.waitForNavigation({
|
|
153
|
+
waitUntil: 'networkidle2',
|
|
154
|
+
timeout,
|
|
155
|
+
});
|
|
156
|
+
}
|
|
157
|
+
}
|
|
@@ -0,0 +1,267 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Cloud WebSocket Connector
|
|
3
|
+
*
|
|
4
|
+
* Implements IFigmaConnector by routing commands through the PluginRelayDO
|
|
5
|
+
* Durable Object. Each method maps to a command sent via fetch() RPC to
|
|
6
|
+
* the relay, which forwards it to the Figma Desktop Bridge plugin over
|
|
7
|
+
* WebSocket.
|
|
8
|
+
*
|
|
9
|
+
* Structurally mirrors WebSocketConnector — same methods, different transport.
|
|
10
|
+
*/
|
|
11
|
+
export class CloudWebSocketConnector {
|
|
12
|
+
constructor(relayStub) {
|
|
13
|
+
this.relayStub = relayStub;
|
|
14
|
+
}
|
|
15
|
+
async initialize() {
|
|
16
|
+
const res = await this.relayStub.fetch('https://relay/relay/status');
|
|
17
|
+
const status = await res.json();
|
|
18
|
+
if (!status.connected) {
|
|
19
|
+
throw new Error('No plugin connected to cloud relay. User must pair the Desktop Bridge plugin first (use figma_pair_plugin tool).');
|
|
20
|
+
}
|
|
21
|
+
}
|
|
22
|
+
getTransportType() {
|
|
23
|
+
return 'websocket';
|
|
24
|
+
}
|
|
25
|
+
// ============================================================================
|
|
26
|
+
// Core execution
|
|
27
|
+
// ============================================================================
|
|
28
|
+
async executeInPluginContext(code) {
|
|
29
|
+
return this.sendCommand('EXECUTE_CODE', { code, timeout: 5000 }, 7000);
|
|
30
|
+
}
|
|
31
|
+
async getVariablesFromPluginUI(fileKey) {
|
|
32
|
+
return this.sendCommand('GET_VARIABLES_DATA', {}, 10000);
|
|
33
|
+
}
|
|
34
|
+
async getVariables(fileKey) {
|
|
35
|
+
const code = `
|
|
36
|
+
(async () => {
|
|
37
|
+
try {
|
|
38
|
+
if (typeof figma === 'undefined') {
|
|
39
|
+
throw new Error('Figma API not available in this context');
|
|
40
|
+
}
|
|
41
|
+
const variables = await figma.variables.getLocalVariablesAsync();
|
|
42
|
+
const collections = await figma.variables.getLocalVariableCollectionsAsync();
|
|
43
|
+
return {
|
|
44
|
+
success: true,
|
|
45
|
+
timestamp: Date.now(),
|
|
46
|
+
fileMetadata: { fileName: figma.root.name, fileKey: figma.fileKey || null },
|
|
47
|
+
variables: variables.map(function(v) { return { id: v.id, name: v.name, key: v.key, resolvedType: v.resolvedType, valuesByMode: v.valuesByMode, variableCollectionId: v.variableCollectionId, scopes: v.scopes, description: v.description, hiddenFromPublishing: v.hiddenFromPublishing }; }),
|
|
48
|
+
variableCollections: collections.map(function(c) { return { id: c.id, name: c.name, key: c.key, modes: c.modes, defaultModeId: c.defaultModeId, variableIds: c.variableIds }; })
|
|
49
|
+
};
|
|
50
|
+
} catch (error) {
|
|
51
|
+
return { success: false, error: error.message };
|
|
52
|
+
}
|
|
53
|
+
})()
|
|
54
|
+
`;
|
|
55
|
+
return this.sendCommand('EXECUTE_CODE', { code, timeout: 30000 }, 32000);
|
|
56
|
+
}
|
|
57
|
+
async executeCodeViaUI(code, timeoutMs = 5000) {
|
|
58
|
+
return this.sendCommand('EXECUTE_CODE', { code, timeout: timeoutMs }, timeoutMs + 2000);
|
|
59
|
+
}
|
|
60
|
+
// ============================================================================
|
|
61
|
+
// Variable operations
|
|
62
|
+
// ============================================================================
|
|
63
|
+
async updateVariable(variableId, modeId, value) {
|
|
64
|
+
return this.sendCommand('UPDATE_VARIABLE', { variableId, modeId, value });
|
|
65
|
+
}
|
|
66
|
+
async createVariable(name, collectionId, resolvedType, options) {
|
|
67
|
+
const params = { name, collectionId, resolvedType };
|
|
68
|
+
if (options) {
|
|
69
|
+
if (options.valuesByMode)
|
|
70
|
+
params.valuesByMode = options.valuesByMode;
|
|
71
|
+
if (options.description)
|
|
72
|
+
params.description = options.description;
|
|
73
|
+
if (options.scopes)
|
|
74
|
+
params.scopes = options.scopes;
|
|
75
|
+
}
|
|
76
|
+
return this.sendCommand('CREATE_VARIABLE', params);
|
|
77
|
+
}
|
|
78
|
+
async deleteVariable(variableId) {
|
|
79
|
+
return this.sendCommand('DELETE_VARIABLE', { variableId });
|
|
80
|
+
}
|
|
81
|
+
async refreshVariables() {
|
|
82
|
+
return this.sendCommand('REFRESH_VARIABLES', {}, 300000);
|
|
83
|
+
}
|
|
84
|
+
async renameVariable(variableId, newName) {
|
|
85
|
+
const result = await this.sendCommand('RENAME_VARIABLE', { variableId, newName });
|
|
86
|
+
if (!result.oldName && result.variable?.oldName)
|
|
87
|
+
result.oldName = result.variable.oldName;
|
|
88
|
+
return result;
|
|
89
|
+
}
|
|
90
|
+
async setVariableDescription(variableId, description) {
|
|
91
|
+
return this.sendCommand('SET_VARIABLE_DESCRIPTION', { variableId, description });
|
|
92
|
+
}
|
|
93
|
+
// ============================================================================
|
|
94
|
+
// Mode operations
|
|
95
|
+
// ============================================================================
|
|
96
|
+
async addMode(collectionId, modeName) {
|
|
97
|
+
return this.sendCommand('ADD_MODE', { collectionId, modeName });
|
|
98
|
+
}
|
|
99
|
+
async renameMode(collectionId, modeId, newName) {
|
|
100
|
+
const result = await this.sendCommand('RENAME_MODE', { collectionId, modeId, newName });
|
|
101
|
+
if (!result.oldName && result.collection?.oldName)
|
|
102
|
+
result.oldName = result.collection.oldName;
|
|
103
|
+
return result;
|
|
104
|
+
}
|
|
105
|
+
// ============================================================================
|
|
106
|
+
// Collection operations
|
|
107
|
+
// ============================================================================
|
|
108
|
+
async createVariableCollection(name, options) {
|
|
109
|
+
const params = { name };
|
|
110
|
+
if (options) {
|
|
111
|
+
if (options.initialModeName)
|
|
112
|
+
params.initialModeName = options.initialModeName;
|
|
113
|
+
if (options.additionalModes)
|
|
114
|
+
params.additionalModes = options.additionalModes;
|
|
115
|
+
}
|
|
116
|
+
return this.sendCommand('CREATE_VARIABLE_COLLECTION', params);
|
|
117
|
+
}
|
|
118
|
+
async deleteVariableCollection(collectionId) {
|
|
119
|
+
return this.sendCommand('DELETE_VARIABLE_COLLECTION', { collectionId });
|
|
120
|
+
}
|
|
121
|
+
// ============================================================================
|
|
122
|
+
// Component operations
|
|
123
|
+
// ============================================================================
|
|
124
|
+
async getComponentFromPluginUI(nodeId) {
|
|
125
|
+
return this.sendCommand('GET_COMPONENT', { nodeId }, 10000);
|
|
126
|
+
}
|
|
127
|
+
async getLocalComponents() {
|
|
128
|
+
return this.sendCommand('GET_LOCAL_COMPONENTS', {}, 300000);
|
|
129
|
+
}
|
|
130
|
+
async setNodeDescription(nodeId, description, descriptionMarkdown) {
|
|
131
|
+
return this.sendCommand('SET_NODE_DESCRIPTION', { nodeId, description, descriptionMarkdown });
|
|
132
|
+
}
|
|
133
|
+
async addComponentProperty(nodeId, propertyName, type, defaultValue, options) {
|
|
134
|
+
const params = { nodeId, propertyName, propertyType: type, defaultValue };
|
|
135
|
+
if (options?.preferredValues)
|
|
136
|
+
params.preferredValues = options.preferredValues;
|
|
137
|
+
return this.sendCommand('ADD_COMPONENT_PROPERTY', params);
|
|
138
|
+
}
|
|
139
|
+
async editComponentProperty(nodeId, propertyName, newValue) {
|
|
140
|
+
return this.sendCommand('EDIT_COMPONENT_PROPERTY', { nodeId, propertyName, newValue });
|
|
141
|
+
}
|
|
142
|
+
async deleteComponentProperty(nodeId, propertyName) {
|
|
143
|
+
return this.sendCommand('DELETE_COMPONENT_PROPERTY', { nodeId, propertyName });
|
|
144
|
+
}
|
|
145
|
+
async instantiateComponent(componentKey, options) {
|
|
146
|
+
const params = { componentKey };
|
|
147
|
+
if (options) {
|
|
148
|
+
if (options.nodeId)
|
|
149
|
+
params.nodeId = options.nodeId;
|
|
150
|
+
if (options.position)
|
|
151
|
+
params.position = options.position;
|
|
152
|
+
if (options.size)
|
|
153
|
+
params.size = options.size;
|
|
154
|
+
if (options.overrides)
|
|
155
|
+
params.overrides = options.overrides;
|
|
156
|
+
if (options.variant)
|
|
157
|
+
params.variant = options.variant;
|
|
158
|
+
if (options.parentId)
|
|
159
|
+
params.parentId = options.parentId;
|
|
160
|
+
}
|
|
161
|
+
return this.sendCommand('INSTANTIATE_COMPONENT', params);
|
|
162
|
+
}
|
|
163
|
+
// ============================================================================
|
|
164
|
+
// Node manipulation
|
|
165
|
+
// ============================================================================
|
|
166
|
+
async resizeNode(nodeId, width, height, withConstraints = true) {
|
|
167
|
+
return this.sendCommand('RESIZE_NODE', { nodeId, width, height, withConstraints });
|
|
168
|
+
}
|
|
169
|
+
async moveNode(nodeId, x, y) {
|
|
170
|
+
return this.sendCommand('MOVE_NODE', { nodeId, x, y });
|
|
171
|
+
}
|
|
172
|
+
async setNodeFills(nodeId, fills) {
|
|
173
|
+
return this.sendCommand('SET_NODE_FILLS', { nodeId, fills });
|
|
174
|
+
}
|
|
175
|
+
async setNodeStrokes(nodeId, strokes, strokeWeight) {
|
|
176
|
+
const params = { nodeId, strokes };
|
|
177
|
+
if (strokeWeight !== undefined)
|
|
178
|
+
params.strokeWeight = strokeWeight;
|
|
179
|
+
return this.sendCommand('SET_NODE_STROKES', params);
|
|
180
|
+
}
|
|
181
|
+
async setNodeOpacity(nodeId, opacity) {
|
|
182
|
+
return this.sendCommand('SET_NODE_OPACITY', { nodeId, opacity });
|
|
183
|
+
}
|
|
184
|
+
async setNodeCornerRadius(nodeId, radius) {
|
|
185
|
+
return this.sendCommand('SET_NODE_CORNER_RADIUS', { nodeId, radius });
|
|
186
|
+
}
|
|
187
|
+
async cloneNode(nodeId) {
|
|
188
|
+
return this.sendCommand('CLONE_NODE', { nodeId });
|
|
189
|
+
}
|
|
190
|
+
async deleteNode(nodeId) {
|
|
191
|
+
return this.sendCommand('DELETE_NODE', { nodeId });
|
|
192
|
+
}
|
|
193
|
+
async renameNode(nodeId, newName) {
|
|
194
|
+
return this.sendCommand('RENAME_NODE', { nodeId, newName });
|
|
195
|
+
}
|
|
196
|
+
async setTextContent(nodeId, characters, options) {
|
|
197
|
+
const params = { nodeId, text: characters };
|
|
198
|
+
if (options) {
|
|
199
|
+
if (options.fontSize)
|
|
200
|
+
params.fontSize = options.fontSize;
|
|
201
|
+
if (options.fontWeight)
|
|
202
|
+
params.fontWeight = options.fontWeight;
|
|
203
|
+
if (options.fontFamily)
|
|
204
|
+
params.fontFamily = options.fontFamily;
|
|
205
|
+
}
|
|
206
|
+
return this.sendCommand('SET_TEXT_CONTENT', params);
|
|
207
|
+
}
|
|
208
|
+
async createChildNode(parentId, nodeType, properties) {
|
|
209
|
+
return this.sendCommand('CREATE_CHILD_NODE', { parentId, nodeType, properties: properties || {} });
|
|
210
|
+
}
|
|
211
|
+
// ============================================================================
|
|
212
|
+
// Screenshot & instance properties
|
|
213
|
+
// ============================================================================
|
|
214
|
+
async captureScreenshot(nodeId, options) {
|
|
215
|
+
const params = { nodeId };
|
|
216
|
+
if (options?.format)
|
|
217
|
+
params.format = options.format;
|
|
218
|
+
if (options?.scale)
|
|
219
|
+
params.scale = options.scale;
|
|
220
|
+
return this.sendCommand('CAPTURE_SCREENSHOT', params, 30000);
|
|
221
|
+
}
|
|
222
|
+
async setInstanceProperties(nodeId, properties) {
|
|
223
|
+
return this.sendCommand('SET_INSTANCE_PROPERTIES', { nodeId, properties });
|
|
224
|
+
}
|
|
225
|
+
// ============================================================================
|
|
226
|
+
// Image fill
|
|
227
|
+
// ============================================================================
|
|
228
|
+
async setImageFill(nodeIds, imageData, scaleMode = 'FILL') {
|
|
229
|
+
return this.sendCommand('SET_IMAGE_FILL', { nodeIds, imageData, scaleMode }, 60000);
|
|
230
|
+
}
|
|
231
|
+
// ============================================================================
|
|
232
|
+
// Design lint
|
|
233
|
+
// ============================================================================
|
|
234
|
+
async lintDesign(nodeId, rules, maxDepth, maxFindings) {
|
|
235
|
+
const params = {};
|
|
236
|
+
if (nodeId)
|
|
237
|
+
params.nodeId = nodeId;
|
|
238
|
+
if (rules)
|
|
239
|
+
params.rules = rules;
|
|
240
|
+
if (maxDepth !== undefined)
|
|
241
|
+
params.maxDepth = maxDepth;
|
|
242
|
+
if (maxFindings !== undefined)
|
|
243
|
+
params.maxFindings = maxFindings;
|
|
244
|
+
return this.sendCommand('LINT_DESIGN', params, 120000);
|
|
245
|
+
}
|
|
246
|
+
// ============================================================================
|
|
247
|
+
// Cache management (no-op for cloud relay)
|
|
248
|
+
// ============================================================================
|
|
249
|
+
clearFrameCache() {
|
|
250
|
+
// No frame cache in cloud relay mode
|
|
251
|
+
}
|
|
252
|
+
// ============================================================================
|
|
253
|
+
// Transport — fetch-based RPC to the relay DO
|
|
254
|
+
// ============================================================================
|
|
255
|
+
async sendCommand(method, params = {}, timeoutMs = 15000) {
|
|
256
|
+
const res = await this.relayStub.fetch('https://relay/relay/command', {
|
|
257
|
+
method: 'POST',
|
|
258
|
+
headers: { 'Content-Type': 'application/json' },
|
|
259
|
+
body: JSON.stringify({ method, params, timeoutMs }),
|
|
260
|
+
});
|
|
261
|
+
const data = await res.json();
|
|
262
|
+
if (data.error) {
|
|
263
|
+
throw new Error(data.error);
|
|
264
|
+
}
|
|
265
|
+
return data.result;
|
|
266
|
+
}
|
|
267
|
+
}
|