Preserve instruction symlink guard through fs seam

This commit is contained in:
Yichen Jiang
2026-07-06 13:55:20 +08:00
parent 3c42352310
commit 901315d8ab
18 changed files with 258 additions and 25 deletions

View File

@@ -92,6 +92,29 @@ describe('stat', () => {
})
})
describe('lstat', () => {
it('reports path metadata without following the final symlink component', async () => {
await writeFile(join(dir, 'real.txt'), 'hello')
await symlink(join(dir, 'real.txt'), join(dir, 'link.txt'))
expect((await fs.lstat('real.txt'))?.type).toBe('file')
expect((await fs.lstat('link.txt'))?.type).toBe('symlink')
expect(await fs.lstat('missing.txt')).toBeUndefined()
})
it('resolves relative paths against opts.cwd and honors a pre-aborted signal', async () => {
const other = await mkdtemp(join(tmpdir(), 'dsh-fs-other-'))
try {
await writeFile(join(other, 'x.txt'), 'in other')
expect((await fs.lstat('x.txt', { cwd: other }))?.type).toBe('file')
await expect(fs.lstat('x.txt', { cwd: other }, AbortSignal.abort())).rejects.toMatchObject({ code: 'FS_ABORTED' })
await expect(fs.lstat(' ')).rejects.toMatchObject({ code: 'FS_NOT_FOUND' })
} finally {
await rm(other, { recursive: true, force: true })
}
})
})
describe('readText / streamText', () => {
it('reads whole-file text', async () => {
await writeFile(join(dir, 'a.txt'), 'one\ntwo\nthree')

View File

@@ -14,6 +14,7 @@ import {
applyLiteralEdit,
listDirectory,
probe,
probeNoFollow,
readForEdit,
readWholeText,
resolveLocalTarget,
@@ -146,6 +147,27 @@ describe('probe', () => {
})
})
describe('probeNoFollow', () => {
it('reports symlinks without following them', async () => {
const real = join(dir, 'real.txt')
const link = join(dir, 'link.txt')
await writeFile(real, 'hi')
await symlink(real, link)
expect((await probeNoFollow(real))?.type).toBe('file')
const linkInfo = await probeNoFollow(link)
expect(linkInfo?.type).toBe('symlink')
expect(typeof linkInfo?.version).toBe('string')
expect(linkInfo?.size).toBeGreaterThan(0)
})
it('returns null for a missing path or a file-valued ancestor path segment', async () => {
expect(await probeNoFollow(join(dir, 'missing'))).toBeNull()
await writeFile(join(dir, 'afile'), 'i am a file')
expect(await probeNoFollow(join(dir, 'afile', 'child.txt'))).toBeNull()
})
})
describe('listDirectory', () => {
it('lists direct children in stable order without reading content', async () => {
const root = join(dir, 'skills')