mirror of
https://gitee.com/zhangsken/opencode.git
synced 2026-10-16 10:30:01 +08:00
fix(core): normalize Windows location keys (#42745)
This commit is contained in:
@@ -1,4 +1,5 @@
|
||||
import { Effect, Layer, LayerMap } from "effect"
|
||||
import path from "path"
|
||||
import { Agent } from "./agent.js"
|
||||
import { AISDK } from "./aisdk.js"
|
||||
import { Catalog } from "./catalog.js"
|
||||
@@ -49,6 +50,7 @@ import { ReadToolFileSystem } from "./tool/read-filesystem.js"
|
||||
import { Tool } from "./tool.js"
|
||||
import { ToolOutput } from "./tool-output.js"
|
||||
import { Vcs } from "./vcs.js"
|
||||
import { AbsolutePath } from "./schema.js"
|
||||
|
||||
export { LocationServiceMap } from "./location-service-map.js"
|
||||
|
||||
@@ -110,11 +112,13 @@ export type LocationError = LayerNode.Error<typeof locationServices>
|
||||
export function buildLocationServiceMap(
|
||||
replacements: LayerNode.Replacements = [],
|
||||
): Layer.Layer<LocationServiceMap.Service> {
|
||||
// Structural Equal is own-key-set sensitive, so `{ directory }` (schema-decoded
|
||||
// payloads omit optional keys) and `{ directory, workspaceID: undefined }` are
|
||||
// different RcMap keys. The RcMap caches by the raw key before the build
|
||||
// callback runs, so canonicalize at the map boundary to the key-present shape.
|
||||
const canonical = (ref: Location.Ref) => Location.Ref.make({ directory: ref.directory, workspaceID: ref.workspaceID })
|
||||
// Structural Equal distinguishes optional-key shape and Windows separator style.
|
||||
// The RcMap caches the raw key before the build callback, so normalize both here.
|
||||
const canonical = (ref: Location.Ref) =>
|
||||
Location.Ref.make({
|
||||
directory: AbsolutePath.make(process.platform === "win32" ? path.normalize(ref.directory) : ref.directory),
|
||||
workspaceID: ref.workspaceID,
|
||||
})
|
||||
return Layer.effect(
|
||||
LocationServiceMap.Service,
|
||||
Effect.map(
|
||||
|
||||
@@ -510,7 +510,7 @@ describe("LocationServiceMap", () => {
|
||||
),
|
||||
)
|
||||
|
||||
it.live("normalizes ref key shapes to one cached location graph", () =>
|
||||
it.live("normalizes equivalent refs to one cached location graph", () =>
|
||||
Effect.acquireRelease(
|
||||
Effect.promise(() => tmpdir()),
|
||||
(dir) => Effect.promise(() => dir[Symbol.asyncDispose]()),
|
||||
@@ -520,16 +520,20 @@ describe("LocationServiceMap", () => {
|
||||
Effect.gen(function* () {
|
||||
const locations = yield* LocationServiceMap.Service
|
||||
const directory = AbsolutePath.make(dir.path)
|
||||
const absent = Location.Ref.make({ directory })
|
||||
const alternate = AbsolutePath.make(directory.replaceAll("\\", "/"))
|
||||
const absent = Location.Ref.make({ directory: alternate })
|
||||
const present = Location.Ref.make({ directory, workspaceID: undefined })
|
||||
// The two shapes are not structurally Equal: own-key sets differ.
|
||||
expect(Object.keys(absent)).toEqual(["directory"])
|
||||
expect(Object.keys(present)).toEqual(["directory", "workspaceID"])
|
||||
expect(Equal.equals(absent, present)).toBe(false)
|
||||
if (process.platform === "win32") expect(absent.directory).not.toBe(present.directory)
|
||||
|
||||
const first = yield* locations.contextEffect(absent)
|
||||
expect(yield* locations.contextEffect(present)).toBe(first)
|
||||
expect(Array.from(yield* RcMap.keys(locations.rcMap))).toHaveLength(1)
|
||||
expect(Array.from(yield* RcMap.keys(locations.rcMap))).toEqual([
|
||||
Location.Ref.make({ directory, workspaceID: undefined }),
|
||||
])
|
||||
|
||||
// Invalidating with the shape opposite to the one that booted must evict.
|
||||
yield* locations.invalidate(present)
|
||||
|
||||
Reference in New Issue
Block a user