@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(`ℹ️
|
|
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
|
-
|
|
546
|
-
|
|
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
|
|
package/companion/mcp-server.mjs
CHANGED
|
@@ -237,7 +237,15 @@ function handleBridgeMessage(rawMessage) {
|
|
|
237
237
|
}
|
|
238
238
|
|
|
239
239
|
if (message.type === 'bridge-event') {
|
|
240
|
-
|
|
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