ソースを参照

fix(fs): keep listDir child keys under stable parent

Tianyi Cui 2 ヶ月 前
コミット
5dfe09959d
2 ファイル変更33 行追加2 行削除
  1. 6 1
      packages/fs/fs-local/src/fsio.ts
  2. 27 1
      packages/fs/fs-local/tests/fsio.spec.ts

+ 6 - 1
packages/fs/fs-local/src/fsio.ts

@@ -196,6 +196,11 @@ function listingIoError(displayPath: string, error: unknown): FsError {
   return new FsError(`cannot list "${displayPath}": ${errorMessage(error)}`, 'FS_IO_ERROR', { cause: error })
 }
 
+async function resolveListedChildTarget(parent: LocalTarget, name: string): Promise<LocalTarget> {
+  const identity = await resolveLocalTarget(parent.targetKey, name)
+  return { displayPath: join(parent.displayPath, name), targetKey: identity.targetKey }
+}
+
 /**
  * List direct children of a directory in stable name order. Each child includes
  * a resolved target plus stat metadata when still available; file contents are
@@ -225,7 +230,7 @@ export async function listDirectory(target: LocalTarget, signal?: AbortSignal):
   for (const entry of entries.sort((left, right) => left.name.localeCompare(right.name))) {
     throwIfAborted(signal, 'list')
     try {
-      const childTarget = await resolveLocalTarget(target.displayPath, entry.name)
+      const childTarget = await resolveListedChildTarget(target, entry.name)
       const childInfo = await probe(childTarget.targetKey)
       result.push({
         name: entry.name,

+ 27 - 1
packages/fs/fs-local/tests/fsio.spec.ts

@@ -6,7 +6,7 @@
  */
 
 import { afterEach, beforeEach, describe, expect, it } from 'vitest'
-import { chmod, mkdtemp, readFile, rm, stat, symlink, writeFile, mkdir, readdir, realpath } from 'node:fs/promises'
+import { chmod, mkdtemp, readFile, rm, stat, symlink, unlink, writeFile, mkdir, readdir, realpath } from 'node:fs/promises'
 import { tmpdir } from 'node:os'
 import { join } from 'node:path'
 import { createServer } from 'node:net'
@@ -167,6 +167,32 @@ describe('listDirectory', () => {
     expect(entries.find(entry => entry.name === 'dir-skill')?.size).toBeUndefined()
   })
 
+  it('derives child target keys from the listed parent identity', async () => {
+    const realOne = join(dir, 'real-one')
+    const realTwo = join(dir, 'real-two')
+    const link = join(dir, 'link')
+    await mkdir(realOne)
+    await mkdir(realTwo)
+    await writeFile(join(realOne, 'same.txt'), 'one')
+    await writeFile(join(realTwo, 'same.txt'), 'different two')
+    await symlink(realOne, link)
+    const target = await resolveLocalTarget(dir, 'link')
+
+    await unlink(link)
+    await symlink(realTwo, link)
+
+    const entries = await listDirectory(target)
+    expect(entries).toHaveLength(1)
+    expect(entries[0]).toMatchObject({
+      name: 'same.txt',
+      target: {
+        displayPath: join(link, 'same.txt'),
+        targetKey: await realpath(join(realOne, 'same.txt')),
+      },
+      size: 3,
+    })
+  })
+
   it('rejects missing, non-directory, and aborted listing requests', async () => {
     await expect(listDirectory(localTarget(join(dir, 'missing')))).rejects.toMatchObject({ code: 'FS_NOT_FOUND' })
     const file = join(dir, 'a.txt')