fix: normalize Windows drive roots (#979)

This commit is contained in:
quecai-niu
2026-07-11 10:40:48 +08:00
committed by GitHub
parent 43e7b6bef7
commit afdcd92fc6
2 changed files with 27 additions and 7 deletions
+14 -2
View File
@@ -18,7 +18,7 @@ vi.mock('../modules/common/opencodeModels', () => ({
listOpencodeModelsForCwd: listOpencodeModelsForCwdMock
}))
import { ApiMachineClient } from './apiMachine'
import { ApiMachineClient, normalizeWindowsDriveRoot } from './apiMachine'
import type { Machine } from './types'
function makeMachine(id: string): Machine {
@@ -37,6 +37,18 @@ function makeMachine(id: string): Machine {
}
}
describe('normalizeWindowsDriveRoot', () => {
it('restores the trailing separator when Windows realpath returns a bare drive', () => {
expect(normalizeWindowsDriveRoot('C:')).toBe('C:\\')
expect(normalizeWindowsDriveRoot('D:')).toBe('D:\\')
})
it('leaves non-drive-root paths unchanged', () => {
expect(normalizeWindowsDriveRoot('C:\\Users')).toBe('C:\\Users')
expect(normalizeWindowsDriveRoot('/tmp/workspace')).toBe('/tmp/workspace')
})
})
async function callListOpencodeModels(client: ApiMachineClient, machineId: string, cwd: string): Promise<unknown> {
// Reach into the private rpc handler manager to dispatch a request.
// Mirrors how the on-socket 'rpc-request' listener invokes handleRequest.
@@ -137,7 +149,7 @@ describe('ApiMachineClient listOpencodeModelsForCwd handler', () => {
})
// The handler realpaths the cwd (security: prevents symlink escape),
// so on macOS /var/folders/... resolves to /private/var/folders/...
expect(listOpencodeModelsForCwdMock).toHaveBeenCalledWith(realpathSync(secondWorkspaceRoot))
expect(listOpencodeModelsForCwdMock).toHaveBeenCalledWith(realpathSync.native(secondWorkspaceRoot))
} finally {
rmSync(secondWorkspaceRoot, { recursive: true, force: true })
client.shutdown()
+13 -5
View File
@@ -41,6 +41,14 @@ interface ListMachineDirectoryRequest {
path: string
}
export function normalizeWindowsDriveRoot(path: string): string {
return /^[A-Za-z]:$/.test(path) ? `${path}\\` : path
}
function canonicalRealpathSync(path: string): string {
return normalizeWindowsDriveRoot(realpathSync.native(path))
}
function normalizeWorkspaceRoots(paths?: string[]): string[] | undefined {
if (!paths?.length) {
return undefined
@@ -48,9 +56,9 @@ function normalizeWorkspaceRoots(paths?: string[]): string[] | undefined {
const normalized = Array.from(new Set(paths.map((path) => {
try {
return realpathSync(path)
return canonicalRealpathSync(path)
} catch {
return resolvePath(path)
return normalizeWindowsDriveRoot(resolvePath(path))
}
})))
@@ -232,7 +240,7 @@ export class ApiMachineClient {
private async resolveForWorkspaceCheck(path: string): Promise<string> {
const absolute = resolvePath(path)
try {
return await realpath(absolute)
return normalizeWindowsDriveRoot(await realpath(absolute))
} catch {
const missing: string[] = []
let cursor = absolute
@@ -240,12 +248,12 @@ export class ApiMachineClient {
missing.unshift(basename(cursor))
cursor = dirname(cursor)
try {
return join(await realpath(cursor), ...missing)
return join(normalizeWindowsDriveRoot(await realpath(cursor)), ...missing)
} catch {
// keep walking to the nearest existing parent
}
}
return absolute
return normalizeWindowsDriveRoot(absolute)
}
}