fix: normalize filepath in FileTime to prevent Windows path mismatch (#20367)
Co-authored-by: JosXa <info@josxa.dev> Co-authored-by: Luke Parker <10430890+Hona@users.noreply.github.com>pull/20555/head
parent
eabf3caeb9
commit
880c0a7477
|
|
@ -4,6 +4,7 @@ import { makeRuntime } from "@/effect/run-service"
|
|||
import { AppFileSystem } from "@/filesystem"
|
||||
import { Flag } from "@/flag/flag"
|
||||
import type { SessionID } from "@/session/schema"
|
||||
import { Filesystem } from "@/util/filesystem"
|
||||
import { Log } from "../util/log"
|
||||
|
||||
export namespace FileTime {
|
||||
|
|
@ -62,6 +63,7 @@ export namespace FileTime {
|
|||
)
|
||||
|
||||
const getLock = Effect.fn("FileTime.lock")(function* (filepath: string) {
|
||||
filepath = Filesystem.normalizePath(filepath)
|
||||
const locks = (yield* InstanceState.get(state)).locks
|
||||
const lock = locks.get(filepath)
|
||||
if (lock) return lock
|
||||
|
|
@ -72,18 +74,21 @@ export namespace FileTime {
|
|||
})
|
||||
|
||||
const read = Effect.fn("FileTime.read")(function* (sessionID: SessionID, file: string) {
|
||||
file = Filesystem.normalizePath(file)
|
||||
const reads = (yield* InstanceState.get(state)).reads
|
||||
log.info("read", { sessionID, file })
|
||||
session(reads, sessionID).set(file, yield* stamp(file))
|
||||
})
|
||||
|
||||
const get = Effect.fn("FileTime.get")(function* (sessionID: SessionID, file: string) {
|
||||
file = Filesystem.normalizePath(file)
|
||||
const reads = (yield* InstanceState.get(state)).reads
|
||||
return reads.get(sessionID)?.get(file)?.read
|
||||
})
|
||||
|
||||
const assert = Effect.fn("FileTime.assert")(function* (sessionID: SessionID, filepath: string) {
|
||||
if (disableCheck) return
|
||||
filepath = Filesystem.normalizePath(filepath)
|
||||
|
||||
const reads = (yield* InstanceState.get(state)).reads
|
||||
const time = reads.get(sessionID)?.get(filepath)
|
||||
|
|
|
|||
|
|
@ -306,6 +306,97 @@ describe("file/time", () => {
|
|||
})
|
||||
})
|
||||
|
||||
describe("path normalization", () => {
|
||||
test("read with forward slashes, assert with backslashes", async () => {
|
||||
await using tmp = await tmpdir()
|
||||
const filepath = path.join(tmp.path, "file.txt")
|
||||
await fs.writeFile(filepath, "content", "utf-8")
|
||||
await touch(filepath, 1_000)
|
||||
|
||||
const forwardSlash = filepath.replaceAll("\\", "/")
|
||||
|
||||
await Instance.provide({
|
||||
directory: tmp.path,
|
||||
fn: async () => {
|
||||
await FileTime.read(sessionID, forwardSlash)
|
||||
// assert with the native backslash path should still work
|
||||
await FileTime.assert(sessionID, filepath)
|
||||
},
|
||||
})
|
||||
})
|
||||
|
||||
test("read with backslashes, assert with forward slashes", async () => {
|
||||
await using tmp = await tmpdir()
|
||||
const filepath = path.join(tmp.path, "file.txt")
|
||||
await fs.writeFile(filepath, "content", "utf-8")
|
||||
await touch(filepath, 1_000)
|
||||
|
||||
const forwardSlash = filepath.replaceAll("\\", "/")
|
||||
|
||||
await Instance.provide({
|
||||
directory: tmp.path,
|
||||
fn: async () => {
|
||||
await FileTime.read(sessionID, filepath)
|
||||
// assert with forward slashes should still work
|
||||
await FileTime.assert(sessionID, forwardSlash)
|
||||
},
|
||||
})
|
||||
})
|
||||
|
||||
test("get returns timestamp regardless of slash direction", async () => {
|
||||
await using tmp = await tmpdir()
|
||||
const filepath = path.join(tmp.path, "file.txt")
|
||||
await fs.writeFile(filepath, "content", "utf-8")
|
||||
|
||||
const forwardSlash = filepath.replaceAll("\\", "/")
|
||||
|
||||
await Instance.provide({
|
||||
directory: tmp.path,
|
||||
fn: async () => {
|
||||
await FileTime.read(sessionID, forwardSlash)
|
||||
const result = await FileTime.get(sessionID, filepath)
|
||||
expect(result).toBeInstanceOf(Date)
|
||||
},
|
||||
})
|
||||
})
|
||||
|
||||
test("withLock serializes regardless of slash direction", async () => {
|
||||
await using tmp = await tmpdir()
|
||||
const filepath = path.join(tmp.path, "file.txt")
|
||||
|
||||
const forwardSlash = filepath.replaceAll("\\", "/")
|
||||
|
||||
await Instance.provide({
|
||||
directory: tmp.path,
|
||||
fn: async () => {
|
||||
const order: number[] = []
|
||||
const hold = gate()
|
||||
const ready = gate()
|
||||
|
||||
const op1 = FileTime.withLock(filepath, async () => {
|
||||
order.push(1)
|
||||
ready.open()
|
||||
await hold.wait
|
||||
order.push(2)
|
||||
})
|
||||
|
||||
await ready.wait
|
||||
|
||||
// Use forward-slash variant -- should still serialize against op1
|
||||
const op2 = FileTime.withLock(forwardSlash, async () => {
|
||||
order.push(3)
|
||||
order.push(4)
|
||||
})
|
||||
|
||||
hold.open()
|
||||
|
||||
await Promise.all([op1, op2])
|
||||
expect(order).toEqual([1, 2, 3, 4])
|
||||
},
|
||||
})
|
||||
})
|
||||
})
|
||||
|
||||
describe("stat() Filesystem.stat pattern", () => {
|
||||
test("reads file modification time via Filesystem.stat()", async () => {
|
||||
await using tmp = await tmpdir()
|
||||
|
|
|
|||
Loading…
Reference in New Issue