From be4ded5ab7e7eeae8b0d3e6dc36d9a0765c3698e Mon Sep 17 00:00:00 2001 From: hangerye Date: Wed, 11 Jun 2025 00:33:15 +0800 Subject: [PATCH] fix: windows install fail --- app/native-server/package.json | 2 +- app/native-server/src/cli.ts | 14 ++++- app/native-server/src/scripts/postinstall.ts | 20 ++++++- app/native-server/src/scripts/run_host.bat | 63 +++++++++++++++++++- app/native-server/src/scripts/utils.ts | 63 +++++++++++++++++--- 5 files changed, 149 insertions(+), 13 deletions(-) diff --git a/app/native-server/package.json b/app/native-server/package.json index 205b5c3..6c4c35d 100644 --- a/app/native-server/package.json +++ b/app/native-server/package.json @@ -1,6 +1,6 @@ { "name": "mcp-chrome-bridge", - "version": "1.0.10", + "version": "1.0.11", "description": "Chrome Native-Messaging host (Node)", "main": "dist/index.js", "bin": { diff --git a/app/native-server/src/cli.ts b/app/native-server/src/cli.ts index 9982910..93c8504 100644 --- a/app/native-server/src/cli.ts +++ b/app/native-server/src/cli.ts @@ -22,7 +22,19 @@ program try { // Detect if running with root/administrator privileges const isRoot = process.getuid && process.getuid() === 0; // Unix/Linux/Mac - const isAdmin = process.platform === 'win32' && require('is-admin')(); // Windows requires additional package + + let isAdmin = false; + if (process.platform === 'win32') { + try { + isAdmin = require('is-admin')(); // Windows requires additional package + } catch (error) { + console.warn( + colorText('Warning: Unable to detect administrator privileges on Windows', 'yellow'), + ); + isAdmin = false; + } + } + const hasElevatedPermissions = isRoot || isAdmin; // If --system option is specified or running with root/administrator privileges diff --git a/app/native-server/src/scripts/postinstall.ts b/app/native-server/src/scripts/postinstall.ts index 32e1e3f..a1eb8d3 100644 --- a/app/native-server/src/scripts/postinstall.ts +++ b/app/native-server/src/scripts/postinstall.ts @@ -24,7 +24,11 @@ function detectGlobalInstall(): boolean { // Method 2: Check if we're in a global pnpm directory structure // pnpm global packages are typically installed in ~/.local/share/pnpm/global/5/node_modules - const globalPnpmPatterns = ['/pnpm/global/', '/.local/share/pnpm/', '/pnpm-global/']; + // Windows: %APPDATA%\pnpm\global\5\node_modules + const globalPnpmPatterns = + process.platform === 'win32' + ? ['\\pnpm\\global\\', '\\pnpm-global\\', '\\AppData\\Roaming\\pnpm\\'] + : ['/pnpm/global/', '/.local/share/pnpm/', '/pnpm-global/']; if (globalPnpmPatterns.some((pattern) => __dirname.includes(pattern))) { return true; @@ -35,6 +39,20 @@ function detectGlobalInstall(): boolean { return true; } + // Method 4: Windows-specific global installation paths + if (process.platform === 'win32') { + const windowsGlobalPatterns = [ + '\\npm\\node_modules\\', + '\\AppData\\Roaming\\npm\\node_modules\\', + '\\Program Files\\nodejs\\node_modules\\', + '\\nodejs\\node_modules\\', + ]; + + if (windowsGlobalPatterns.some((pattern) => __dirname.includes(pattern))) { + return true; + } + } + return false; } diff --git a/app/native-server/src/scripts/run_host.bat b/app/native-server/src/scripts/run_host.bat index b1b6ced..b30b158 100644 --- a/app/native-server/src/scripts/run_host.bat +++ b/app/native-server/src/scripts/run_host.bat @@ -63,22 +63,83 @@ if not defined NODE_EXEC ( ) ) ) + + REM Check for Volta (another Node.js version manager) + if exist "%LOCALAPPDATA%\Volta\bin\node.exe" ( + if not defined NODE_EXEC ( + set "NODE_EXEC=%LOCALAPPDATA%\Volta\bin\node.exe" + echo Found Volta node at %LOCALAPPDATA%\Volta\bin\node.exe >> "%WRAPPER_LOG%" + ) + ) + + REM Check for fnm (Fast Node Manager) + if exist "%LOCALAPPDATA%\fnm_multishells" ( + for /f "delims=" %%v in ('dir /b /ad "%LOCALAPPDATA%\fnm_multishells" 2^>nul ^| sort /r') do ( + if not defined NODE_EXEC if exist "%LOCALAPPDATA%\fnm_multishells\%%v\node.exe" ( + set "NODE_EXEC=%LOCALAPPDATA%\fnm_multishells\%%v\node.exe" + echo Found fnm node at %LOCALAPPDATA%\fnm_multishells\%%v\node.exe >> "%WRAPPER_LOG%" + goto :node_found + ) + ) + ) + + REM Check for Scoop installation + if exist "%USERPROFILE%\scoop\apps\nodejs\current\node.exe" ( + if not defined NODE_EXEC ( + set "NODE_EXEC=%USERPROFILE%\scoop\apps\nodejs\current\node.exe" + echo Found Scoop node at %USERPROFILE%\scoop\apps\nodejs\current\node.exe >> "%WRAPPER_LOG%" + ) + ) + + REM Check for Chocolatey installation + if exist "%ProgramData%\chocolatey\lib\nodejs\tools\node.exe" ( + if not defined NODE_EXEC ( + set "NODE_EXEC=%ProgramData%\chocolatey\lib\nodejs\tools\node.exe" + echo Found Chocolatey node at %ProgramData%\chocolatey\lib\nodejs\tools\node.exe >> "%WRAPPER_LOG%" + ) + ) ) :node_found if not defined NODE_EXEC ( echo ERROR: Node.js executable not found! >> "%WRAPPER_LOG%" echo Searched 'where node.exe' and common installation paths. >> "%WRAPPER_LOG%" + echo Searched paths: >> "%WRAPPER_LOG%" + echo - %ProgramFiles%\nodejs\node.exe >> "%WRAPPER_LOG%" + echo - %ProgramFiles(x86)%\nodejs\node.exe >> "%WRAPPER_LOG%" + echo - %LOCALAPPDATA%\Programs\nodejs\node.exe >> "%WRAPPER_LOG%" + echo - %APPDATA%\nvm\* >> "%WRAPPER_LOG%" + echo - %LOCALAPPDATA%\Volta\bin\node.exe >> "%WRAPPER_LOG%" + echo - %LOCALAPPDATA%\fnm_multishells\* >> "%WRAPPER_LOG%" + echo - %USERPROFILE%\scoop\apps\nodejs\current\node.exe >> "%WRAPPER_LOG%" + echo - %ProgramData%\chocolatey\lib\nodejs\tools\node.exe >> "%WRAPPER_LOG%" + echo Please install Node.js or ensure it's in your PATH. >> "%WRAPPER_LOG%" + echo You can download Node.js from: https://nodejs.org/ >> "%WRAPPER_LOG%" exit /B 1 ) echo Using Node executable: %NODE_EXEC% >> "%WRAPPER_LOG%" echo Node version found by script: >> "%WRAPPER_LOG%" call "%NODE_EXEC%" -v >> "%WRAPPER_LOG%" 2>>&1 + +REM Verify Node.js script exists +if not exist "%NODE_SCRIPT%" ( + echo ERROR: Node.js script not found: %NODE_SCRIPT% >> "%WRAPPER_LOG%" + echo Please ensure the native host is properly installed. >> "%WRAPPER_LOG%" + exit /B 1 +) + +echo Node.js script exists: %NODE_SCRIPT% >> "%WRAPPER_LOG%" echo Executing: "%NODE_EXEC%" "%NODE_SCRIPT%" >> "%WRAPPER_LOG%" +echo ==================== Starting Native Host ==================== >> "%WRAPPER_LOG%" REM Execute the Node.js script. Stdout goes to Chrome. Stderr goes to the log file. call "%NODE_EXEC%" "%NODE_SCRIPT%" 2>> "%STDERR_LOG%" +set "EXIT_CODE=%ERRORLEVEL%" + +echo ==================== Native Host Exited ==================== >> "%WRAPPER_LOG%" +echo Exit code: %EXIT_CODE% >> "%WRAPPER_LOG%" +echo Execution completed at %DATE% %TIME% >> "%WRAPPER_LOG%" endlocal -exit /B %ERRORLEVEL% \ No newline at end of file +exit /B %EXIT_CODE% \ No newline at end of file diff --git a/app/native-server/src/scripts/utils.ts b/app/native-server/src/scripts/utils.ts index 016b76d..5772eb3 100644 --- a/app/native-server/src/scripts/utils.ts +++ b/app/native-server/src/scripts/utils.ts @@ -199,6 +199,28 @@ export async function createManifestContent(): Promise { }; } +/** + * 验证Windows注册表项是否存在 + */ +function verifyWindowsRegistryEntry(registryKey: string, expectedPath: string): boolean { + if (os.platform() !== 'win32') { + return true; // 非Windows平台跳过验证 + } + + try { + const result = execSync(`reg query "${registryKey}" /ve`, { encoding: 'utf8', stdio: 'pipe' }); + const lines = result.split('\n'); + for (const line of lines) { + if (line.includes('REG_SZ') && line.includes(expectedPath.replace(/\\/g, '\\\\'))) { + return true; + } + } + return false; + } catch (error) { + return false; + } +} + /** * 尝试注册用户级别的Native Messaging主机 */ @@ -226,15 +248,25 @@ export async function tryRegisterUserLevelHost(): Promise { if (os.platform() === 'win32') { const registryKey = `HKCU\\Software\\Google\\Chrome\\NativeMessagingHosts\\${HOST_NAME}`; try { - execSync( - `reg add "${registryKey}" /ve /t REG_SZ /d "${manifestPath.replace(/\\/g, '\\\\')}" /f`, - { stdio: 'ignore' }, - ); - console.log(colorText('✓ Successfully created Windows registry entry', 'green')); + // 确保路径使用正确的转义格式 + const escapedPath = manifestPath.replace(/\\/g, '\\\\'); + const regCommand = `reg add "${registryKey}" /ve /t REG_SZ /d "${escapedPath}" /f`; + + console.log(colorText(`Executing registry command: ${regCommand}`, 'blue')); + execSync(regCommand, { stdio: 'pipe' }); + + // 验证注册表项是否创建成功 + if (verifyWindowsRegistryEntry(registryKey, manifestPath)) { + console.log(colorText('✓ Successfully created Windows registry entry', 'green')); + } else { + console.log(colorText('⚠️ Registry entry created but verification failed', 'yellow')); + } } catch (error: any) { console.log( colorText(`⚠️ Unable to create Windows registry entry: ${error.message}`, 'yellow'), ); + console.log(colorText(`Registry key: ${registryKey}`, 'yellow')); + console.log(colorText(`Manifest path: ${manifestPath}`, 'yellow')); return false; // Windows上如果注册表项创建失败,整个注册过程应该视为失败 } } @@ -301,7 +333,7 @@ export async function registerWithElevatedPermissions(): Promise { // 准备命令 const command = os.platform() === 'win32' - ? `mkdir -p "${path.dirname(manifestPath)}" && copy "${tempManifestPath}" "${manifestPath}"` + ? `if not exist "${path.dirname(manifestPath)}" mkdir "${path.dirname(manifestPath)}" && copy "${tempManifestPath}" "${manifestPath}"` : `mkdir -p "${path.dirname(manifestPath)}" && cp "${tempManifestPath}" "${manifestPath}" && chmod 644 "${manifestPath}"`; if (hasElevatedPermissions) { @@ -347,17 +379,29 @@ export async function registerWithElevatedPermissions(): Promise { // 6. Windows特殊处理 - 设置系统级注册表 if (os.platform() === 'win32') { const registryKey = `HKLM\\Software\\Google\\Chrome\\NativeMessagingHosts\\${HOST_NAME}`; - const regCommand = `reg add "${registryKey}" /ve /t REG_SZ /d "${manifestPath.replace(/\\/g, '\\\\')}" /f`; + // 确保路径使用正确的转义格式 + const escapedPath = manifestPath.replace(/\\/g, '\\\\'); + const regCommand = `reg add "${registryKey}" /ve /t REG_SZ /d "${escapedPath}" /f`; + + console.log(colorText(`Creating system registry entry: ${registryKey}`, 'blue')); + console.log(colorText(`Manifest path: ${manifestPath}`, 'blue')); if (hasElevatedPermissions) { // 已经有管理员权限,直接执行注册表命令 try { - execSync(regCommand, { stdio: 'ignore' }); - console.log(colorText('Windows registry entry created successfully!', 'green')); + execSync(regCommand, { stdio: 'pipe' }); + + // 验证注册表项是否创建成功 + if (verifyWindowsRegistryEntry(registryKey, manifestPath)) { + console.log(colorText('Windows registry entry created successfully!', 'green')); + } else { + console.log(colorText('⚠️ Registry entry created but verification failed', 'yellow')); + } } catch (error: any) { console.error( colorText(`Windows registry entry creation failed: ${error.message}`, 'red'), ); + console.error(colorText(`Command: ${regCommand}`, 'red')); throw error; } } else { @@ -368,6 +412,7 @@ export async function registerWithElevatedPermissions(): Promise { console.error( colorText(`Windows registry entry creation failed: ${error.message}`, 'red'), ); + console.error(colorText(`Command: ${regCommand}`, 'red')); reject(error); } else { console.log(colorText('Windows registry entry created successfully!', 'green'));