fix shims for extensions on windows (#3009)
This commit is contained in:
@@ -24,6 +24,7 @@ import { startGoosed } from './goosed';
|
||||
import { getBinaryPath } from './utils/binaryPath';
|
||||
import { loadShellEnv } from './utils/loadEnv';
|
||||
import log from './utils/logger';
|
||||
import { ensureWinShims } from './utils/winShims';
|
||||
import { addRecentDir, loadRecentDirs } from './utils/recentDirs';
|
||||
import {
|
||||
createEnvironmentMenu,
|
||||
@@ -1313,6 +1314,9 @@ const registerGlobalHotkey = (accelerator: string) => {
|
||||
};
|
||||
|
||||
app.whenReady().then(async () => {
|
||||
// Ensure Windows shims are available before any MCP processes are spawned
|
||||
await ensureWinShims();
|
||||
|
||||
// Register update IPC handlers once (but don't setup auto-updater yet)
|
||||
registerUpdateIpcHandlers();
|
||||
|
||||
|
||||
@@ -22,16 +22,21 @@ export const getBinaryPath = (app: Electron.App, binaryName: string): string =>
|
||||
throw new Error(`Invalid binary name: ${binaryName}`);
|
||||
}
|
||||
|
||||
const isWindows = process.platform === 'win32';
|
||||
|
||||
const possiblePaths: string[] = [];
|
||||
if (isWindows) {
|
||||
addPaths(isWindows, possiblePaths, `${binaryName}.exe`, app);
|
||||
addPaths(isWindows, possiblePaths, `${binaryName}.cmd`, app);
|
||||
} else {
|
||||
addPaths(isWindows, possiblePaths, binaryName, app);
|
||||
// On Windows, rely on PATH we just patched in ensureWinShims for command-line tools
|
||||
// but use explicit resources/bin path for goosed.exe
|
||||
if (process.platform === 'win32') {
|
||||
// For goosed.exe, always use the explicit resources/bin path
|
||||
if (binaryName === 'goosed') {
|
||||
return path.join(process.resourcesPath, 'bin', 'goosed.exe');
|
||||
}
|
||||
// For other binaries (uvx, npx), rely on PATH we just patched
|
||||
return binaryName;
|
||||
}
|
||||
|
||||
// For non-Windows platforms, use the original logic
|
||||
const possiblePaths: string[] = [];
|
||||
addPaths(false, possiblePaths, binaryName, app);
|
||||
|
||||
for (const binPath of possiblePaths) {
|
||||
try {
|
||||
// Security: Resolve the path and validate it's within expected directories
|
||||
|
||||
@@ -0,0 +1,112 @@
|
||||
import fs from 'node:fs';
|
||||
import path from 'node:path';
|
||||
import os from 'node:os';
|
||||
import { spawn } from 'child_process';
|
||||
import log from './logger';
|
||||
|
||||
/**
|
||||
* Ensures Windows shims are available in %LOCALAPPDATA%\Goose\bin
|
||||
* This allows the bundled executables to be found via PATH regardless of where Goose is installed
|
||||
*/
|
||||
export async function ensureWinShims(): Promise<void> {
|
||||
if (process.platform !== 'win32') return;
|
||||
|
||||
const srcDir = path.join(process.resourcesPath, 'bin'); // existing dir
|
||||
const tgtDir = path.join(
|
||||
process.env.LOCALAPPDATA ?? path.join(os.homedir(), 'AppData', 'Local'),
|
||||
'Goose',
|
||||
'bin'
|
||||
);
|
||||
|
||||
try {
|
||||
await fs.promises.mkdir(tgtDir, { recursive: true });
|
||||
|
||||
// Only copy the command-line tools, NOT goosed.exe (which should always be used locally)
|
||||
const shims = ['uvx.exe', 'npx.cmd', 'install-node.cmd'];
|
||||
|
||||
await Promise.all(
|
||||
shims.map(async (shim) => {
|
||||
const src = path.join(srcDir, shim);
|
||||
const dst = path.join(tgtDir, shim);
|
||||
try {
|
||||
// Check if source file exists before attempting to copy
|
||||
await fs.promises.access(src);
|
||||
await fs.promises.copyFile(src, dst); // overwrites with newer build
|
||||
log.info(`Copied Windows shim: ${shim} to ${dst}`);
|
||||
} catch (e) {
|
||||
log.error(`Failed to copy shim ${shim}`, e);
|
||||
}
|
||||
})
|
||||
);
|
||||
|
||||
// Prepend to PATH **for this process & all children**.
|
||||
// Make sure our bin directory is at the VERY BEGINNING of PATH
|
||||
const currentPath = process.env.PATH ?? '';
|
||||
if (!currentPath.toLowerCase().includes(tgtDir.toLowerCase())) {
|
||||
process.env.PATH = `${tgtDir}${path.delimiter}${currentPath}`;
|
||||
log.info(`Added ${tgtDir} to PATH for current process`);
|
||||
} else {
|
||||
// If it's already in PATH, make sure it's at the beginning
|
||||
const pathParts = currentPath.split(path.delimiter);
|
||||
const binDirIndex = pathParts.findIndex((p) => p.toLowerCase() === tgtDir.toLowerCase());
|
||||
|
||||
if (binDirIndex > 0) {
|
||||
// Remove it from its current position and add to beginning
|
||||
pathParts.splice(binDirIndex, 1);
|
||||
process.env.PATH = `${tgtDir}${path.delimiter}${pathParts.join(path.delimiter)}`;
|
||||
log.info(`Moved ${tgtDir} to beginning of PATH for current process`);
|
||||
}
|
||||
}
|
||||
|
||||
// Optional: Persist PATH for user's external PowerShell/CMD sessions
|
||||
await persistPathForUser(tgtDir);
|
||||
} catch (error) {
|
||||
log.error('Failed to ensure Windows shims:', error);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Optionally persist the Goose bin directory to the user's PATH environment variable
|
||||
* This allows users to run uvx, npx, goosed from external PowerShell/CMD sessions
|
||||
*/
|
||||
async function persistPathForUser(binDir: string): Promise<void> {
|
||||
try {
|
||||
const psScript = `
|
||||
$bin = "${binDir.replace(/\\/g, '\\\\')}"
|
||||
if (-not ($Env:Path -split ';' | Where-Object { $_ -ieq $bin })) {
|
||||
# Add to beginning of PATH for priority
|
||||
setx PATH "$bin;$Env:Path" >$null
|
||||
Write-Host "Added Goose bin directory to beginning of user PATH"
|
||||
} else {
|
||||
# If already in PATH, ensure it's at the beginning
|
||||
$pathParts = $Env:Path -split ';'
|
||||
$binIndex = 0
|
||||
for ($i = 0; $i -lt $pathParts.Count; $i++) {
|
||||
if ($pathParts[$i] -ieq $bin) {
|
||||
$binIndex = $i
|
||||
break
|
||||
}
|
||||
}
|
||||
|
||||
if ($binIndex -gt 0) {
|
||||
# Remove from current position and add to beginning
|
||||
$pathParts = @($pathParts[$binIndex]) + @($pathParts | Where-Object { $_ -ine $bin })
|
||||
$newPath = $pathParts -join ';'
|
||||
setx PATH $newPath >$null
|
||||
Write-Host "Moved Goose bin directory to beginning of user PATH"
|
||||
} else {
|
||||
Write-Host "Goose bin directory already at beginning of user PATH"
|
||||
}
|
||||
}
|
||||
`;
|
||||
|
||||
spawn('powershell', ['-NoProfile', '-NonInteractive', '-Command', psScript], {
|
||||
windowsHide: true,
|
||||
shell: false,
|
||||
});
|
||||
|
||||
log.info('Attempted to persist Goose bin directory to user PATH');
|
||||
} catch (error) {
|
||||
log.warn('Failed to persist PATH for user (non-critical):', error);
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user