From d0fb7724f8a06357841ddd678933be52479be58a Mon Sep 17 00:00:00 2001 From: FynWorld Date: Wed, 12 Aug 2026 11:45:53 -0300 Subject: [PATCH] Resolve Electron through Node module lookup --- mcp-lib.mjs | 9 ++++++-- tests/mcp-lib.test.mjs | 47 +++++++++++++++++++++++++++++++++++++++++- 2 files changed, 53 insertions(+), 3 deletions(-) diff --git a/mcp-lib.mjs b/mcp-lib.mjs index 4167430..141c811 100644 --- a/mcp-lib.mjs +++ b/mcp-lib.mjs @@ -1,12 +1,14 @@ import { spawn } from 'node:child_process'; import path from 'node:path'; import fs from 'node:fs/promises'; +import { createRequire } from 'node:module'; import { fileURLToPath } from 'node:url'; import { readState, readToken } from './state.mjs'; const __filename = fileURLToPath(import.meta.url); const __dirname = path.dirname(__filename); +const packageRequire = createRequire(import.meta.url); async function sleep(ms) { return new Promise((r) => setTimeout(r, ms)); @@ -31,8 +33,11 @@ async function electronLaunch({ platform, allowFallback = false }) { }; } - const electronCli = path.resolve(__dirname, 'node_modules', 'electron', 'cli.js'); - if (await fileExists(electronCli)) { + let electronCli = null; + try { + electronCli = packageRequire.resolve('electron/cli.js'); + } catch {} + if (electronCli && await fileExists(electronCli)) { return { command: process.execPath, argsPrefix: [electronCli], shell: false }; } diff --git a/tests/mcp-lib.test.mjs b/tests/mcp-lib.test.mjs index 54124c2..b85a4b5 100644 --- a/tests/mcp-lib.test.mjs +++ b/tests/mcp-lib.test.mjs @@ -3,7 +3,7 @@ import assert from 'node:assert/strict'; import os from 'node:os'; import path from 'node:path'; import fs from 'node:fs/promises'; -import { fileURLToPath } from 'node:url'; +import { fileURLToPath, pathToFileURL } from 'node:url'; import { ensureToken, writeState } from '../state.mjs'; import { ensureDesktopRunning, requestJson } from '../mcp-lib.mjs'; @@ -138,6 +138,51 @@ test('mcp-lib: Windows spawn uses Node-hosted Electron CLI without shell', async assert.equal(spawnShell, false); }); +test('mcp-lib: ensureDesktopRunning resolves Electron from a hoisted parent node_modules', async () => { + const fixtureRoot = await tempDir(); + const desktopRoot = path.join(fixtureRoot, 'node_modules', '@agentify', 'desktop'); + const electronRoot = path.join(fixtureRoot, 'node_modules', 'electron'); + await fs.mkdir(desktopRoot, { recursive: true }); + await fs.mkdir(electronRoot, { recursive: true }); + + const sourceRoot = path.resolve(path.dirname(fileURLToPath(import.meta.url)), '..'); + await fs.copyFile(path.join(sourceRoot, 'mcp-lib.mjs'), path.join(desktopRoot, 'mcp-lib.mjs')); + await fs.copyFile(path.join(sourceRoot, 'state.mjs'), path.join(desktopRoot, 'state.mjs')); + await fs.writeFile(path.join(desktopRoot, 'main.mjs'), '// fixture desktop entry\n', 'utf8'); + await fs.writeFile(path.join(electronRoot, 'package.json'), JSON.stringify({ name: 'electron', version: '0.0.0' }), 'utf8'); + await fs.writeFile(path.join(electronRoot, 'cli.js'), '// fixture\n', 'utf8'); + + const fixtureMcp = await import(`${pathToFileURL(path.join(desktopRoot, 'mcp-lib.mjs')).href}?fixture=${Date.now()}`); + const fixtureState = await import(`${pathToFileURL(path.join(desktopRoot, 'state.mjs')).href}?fixture=${Date.now()}`); + const stateDir = await tempDir(); + const token = 't'; + await fixtureState.ensureToken(stateDir); + await fs.writeFile(path.join(stateDir, 'token.txt'), `${token}\n`, 'utf8'); + await fixtureState.writeState({ ok: true, port: 12345, serverId: 'sid-old' }, stateDir); + + let fetchServerId = 'sid-wrong'; + let spawnedCmd = null; + let spawnedArgs = null; + const conn = await fixtureMcp.ensureDesktopRunning({ + stateDir, + fetchImpl: makeFetch({ getServerId: () => fetchServerId, acceptToken: token }), + platform: 'win32', + spawnImpl: (cmd, args) => { + spawnedCmd = cmd; + spawnedArgs = args; + fetchServerId = 'sid-new'; + void fixtureState.writeState({ ok: true, port: 12345, serverId: 'sid-new' }, stateDir); + return { unref() {} }; + }, + timeoutMs: 3000 + }); + + assert.equal(conn.serverId, 'sid-new'); + assert.equal(spawnedCmd, process.execPath); + assert.equal(spawnedArgs?.[0], path.join(electronRoot, 'cli.js')); + assert.equal(spawnedArgs?.[1], path.join(desktopRoot, 'main.mjs')); +}); + test('mcp-lib: ensureDesktopRunning resolves bundled electron relative to desktop package, not cwd', async () => { const dir = await tempDir(); const token = 't';