From 6113e270066b903667ae2fb58c4f779789b11185 Mon Sep 17 00:00:00 2001 From: 777genius Date: Sat, 25 Apr 2026 20:31:14 +0300 Subject: [PATCH] fix(windows): resolve cli shim path handling --- .../codexAppServer/CodexBinaryResolver.ts | 25 ++++++++++++++++--- .../__tests__/CodexBinaryResolver.test.ts | 4 +-- src/main/utils/childProcess.ts | 1 + 3 files changed, 25 insertions(+), 5 deletions(-) diff --git a/src/main/services/infrastructure/codexAppServer/CodexBinaryResolver.ts b/src/main/services/infrastructure/codexAppServer/CodexBinaryResolver.ts index 5a108abf..ee48a84a 100644 --- a/src/main/services/infrastructure/codexAppServer/CodexBinaryResolver.ts +++ b/src/main/services/infrastructure/codexAppServer/CodexBinaryResolver.ts @@ -43,10 +43,29 @@ function expandWindowsExtensions(candidate: string): string[] { return [...pathext.map((ext) => `${candidate}${ext.toLowerCase()}`), candidate]; } +function isPathLikeCandidate(candidate: string): boolean { + if (process.platform === 'win32') { + return path.win32.isAbsolute(candidate) || candidate.includes('\\') || candidate.includes('/'); + } + return path.isAbsolute(candidate) || candidate.includes(path.sep); +} + +function getPathEntries(): string[] { + const delimiter = process.platform === 'win32' ? ';' : path.delimiter; + return (process.env.PATH ?? '').split(delimiter).filter(Boolean); +} + +function resolvePathEntryCandidate(pathEntry: string, candidate: string): string { + if (process.platform === 'win32') { + return path.win32.join(pathEntry, candidate); + } + return path.join(pathEntry, candidate); +} + async function verifyBinary(candidate: string): Promise { const expandedCandidates = expandWindowsExtensions(candidate); - if (path.isAbsolute(candidate) || candidate.includes(path.sep)) { + if (isPathLikeCandidate(candidate)) { for (const expandedCandidate of expandedCandidates) { if (await fileExists(expandedCandidate)) { return expandedCandidate; @@ -55,10 +74,10 @@ async function verifyBinary(candidate: string): Promise { return null; } - const pathEntries = (process.env.PATH ?? '').split(path.delimiter).filter(Boolean); + const pathEntries = getPathEntries(); for (const pathEntry of pathEntries) { for (const expandedCandidate of expandedCandidates) { - const resolvedCandidate = path.join(pathEntry, expandedCandidate); + const resolvedCandidate = resolvePathEntryCandidate(pathEntry, expandedCandidate); if (await fileExists(resolvedCandidate)) { return resolvedCandidate; } diff --git a/src/main/services/infrastructure/codexAppServer/__tests__/CodexBinaryResolver.test.ts b/src/main/services/infrastructure/codexAppServer/__tests__/CodexBinaryResolver.test.ts index ec2aeaef..7ac04e11 100644 --- a/src/main/services/infrastructure/codexAppServer/__tests__/CodexBinaryResolver.test.ts +++ b/src/main/services/infrastructure/codexAppServer/__tests__/CodexBinaryResolver.test.ts @@ -42,8 +42,8 @@ describe('CodexBinaryResolver', () => { it('prefers the Windows command shim over the extensionless POSIX shim on PATH', async () => { const binDir = 'C:\\Program Files\\nodejs'; - const extensionless = path.join(binDir, 'codex'); - const cmdShim = path.join(binDir, 'codex.cmd'); + const extensionless = path.win32.join(binDir, 'codex'); + const cmdShim = path.win32.join(binDir, 'codex.cmd'); process.env.PATH = binDir; accessMock.mockImplementation((filePath, mode) => { diff --git a/src/main/utils/childProcess.ts b/src/main/utils/childProcess.ts index e6795bc9..58dd66f2 100644 --- a/src/main/utils/childProcess.ts +++ b/src/main/utils/childProcess.ts @@ -112,6 +112,7 @@ function resolveCmdPathTemplate(template: string, launcherDir: string): string { .replace(/%SCRIPT_DIR%/gi, dirWithSep) .replace(/%~dp0/gi, dirWithSep) .replace(/%dp0%/gi, dirWithSep) + .replace(/\\/g, path.sep) ); }