@createlex/figma-swiftui-mcp 1.0.6 → 1.0.7

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.
@@ -51,13 +51,50 @@ async function waitForExistingBridge({ host, port }) {
51
51
  throw new Error(`HTTP ${response.status}`);
52
52
  }
53
53
  const data = await response.json();
54
+
55
+ // Verify WebSocket is also healthy, not just HTTP
56
+ await new Promise((resolve, reject) => {
57
+ const testWs = new WebSocket(`ws://${host}:${port}/bridge`);
58
+ const timeout = setTimeout(() => {
59
+ testWs.terminate();
60
+ reject(new Error('WebSocket health check timed out'));
61
+ }, 3000);
62
+ testWs.on('open', () => {
63
+ clearTimeout(timeout);
64
+ testWs.close();
65
+ resolve();
66
+ });
67
+ testWs.on('error', (err) => {
68
+ clearTimeout(timeout);
69
+ reject(new Error(`WebSocket health check failed: ${err.message}`));
70
+ });
71
+ });
72
+
54
73
  return {
55
74
  ok: true,
56
75
  alreadyRunning: true,
57
76
  info: data,
58
77
  };
59
78
  } catch (error) {
60
- throw new Error(`Port ${port} is already in use and does not look like a Figma SwiftUI bridge`);
79
+ throw new Error(`Port ${port} is already in use and does not look like a healthy Figma SwiftUI bridge (${error.message})`);
80
+ }
81
+ }
82
+
83
+ async function killStaleAndRetry({ host, port, logger }) {
84
+ const { execSync } = require('child_process');
85
+ try {
86
+ const lsofOutput = execSync(`lsof -ti :${port}`, { encoding: 'utf8' }).trim();
87
+ const pids = lsofOutput.split('\n').filter(Boolean);
88
+ if (pids.length > 0) {
89
+ logger.warn(`⚠️ Killing ${pids.length} stale process(es) on port ${port}: ${pids.join(', ')}`);
90
+ for (const pid of pids) {
91
+ try { process.kill(Number(pid), 'SIGTERM'); } catch (_) {}
92
+ }
93
+ // Wait for port to free up
94
+ await new Promise((r) => setTimeout(r, 1500));
95
+ }
96
+ } catch (_) {
97
+ // lsof returns non-zero if no matches — port is already free
61
98
  }
62
99
  }
63
100
 
@@ -527,7 +564,7 @@ function startBridgeServer(options = {}) {
527
564
  if (error && error.code === 'EADDRINUSE') {
528
565
  try {
529
566
  const running = await waitForExistingBridge({ host, port });
530
- logger.warn(`ℹ️ Using existing bridge at http://${host}:${port}`);
567
+ logger.warn(`ℹ️ Using existing healthy bridge at http://${host}:${port}`);
531
568
  resolve({
532
569
  app,
533
570
  server: null,
@@ -542,8 +579,47 @@ function startBridgeServer(options = {}) {
542
579
  });
543
580
  return;
544
581
  } catch (existingError) {
545
- reject(existingError);
546
- return;
582
+ // Existing bridge is unhealthy — kill stale processes and start fresh
583
+ logger.warn(`⚠️ ${existingError.message}`);
584
+ try {
585
+ await killStaleAndRetry({ host, port, logger });
586
+ // Re-attempt listen after killing stale processes
587
+ server.listen(port, host, () => {
588
+ logger.info(`🚀 Figma SwiftUI bridge running at http://${host}:${port} (recovered from stale)`);
589
+ if (projectPath) {
590
+ logger.info(`📂 Project path: ${projectPath}`);
591
+ }
592
+ logger.info(`🌉 Bridge ready at ws://${host}:${port}/bridge`);
593
+ resolve({
594
+ app,
595
+ server,
596
+ bridgeWss,
597
+ port,
598
+ host,
599
+ alreadyRunning: false,
600
+ getProjectPath: () => projectPath,
601
+ getBridgeInfo: () => ({
602
+ protocolVersion: BRIDGE_PROTOCOL_VERSION,
603
+ pluginConnected: !!pluginBridgeClient,
604
+ connectedAgents: agentBridgeClients.size,
605
+ pendingRequests: pendingBridgeRequests.size,
606
+ supportedActions: SUPPORTED_BRIDGE_ACTIONS,
607
+ }),
608
+ close: () => new Promise((closeResolve, closeReject) => {
609
+ bridgeWss.close(() => {
610
+ server.close((closeError) => {
611
+ if (closeError) closeReject(closeError);
612
+ else closeResolve();
613
+ });
614
+ });
615
+ }),
616
+ });
617
+ });
618
+ return;
619
+ } catch (retryError) {
620
+ reject(new Error(`Failed to recover bridge: ${retryError.message}`));
621
+ return;
622
+ }
547
623
  }
548
624
  }
549
625
 
@@ -237,7 +237,15 @@ function handleBridgeMessage(rawMessage) {
237
237
  }
238
238
 
239
239
  if (message.type === 'bridge-event') {
240
- console.error(`[figma-swiftui-mcp] Bridge event ${message.event}: ${JSON.stringify(message.data)}`);
240
+ // Log bridge events concisely — skip full JSON payloads for noisy events
241
+ const summary = message.data && message.event === 'selectionchange'
242
+ ? `${message.data.selection?.count ?? 0} node(s) selected`
243
+ : message.data && message.event === 'currentpagechange'
244
+ ? `page "${message.data.currentPage?.name || 'unknown'}"`
245
+ : '';
246
+ if (summary) {
247
+ console.error(`[figma-swiftui-mcp] ${message.event}: ${summary}`);
248
+ }
241
249
  }
242
250
  }
243
251
 
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@createlex/figma-swiftui-mcp",
3
- "version": "1.0.6",
3
+ "version": "1.0.7",
4
4
  "description": "CreateLex MCP runtime for Figma-to-SwiftUI generation and Xcode export",
5
5
  "bin": {
6
6
  "figma-swiftui-mcp": "bin/figma-swiftui-mcp.js"