mirror of
https://github.com/wu736139669/hapi.git
synced 2026-08-05 06:24:37 +00:00
feat(web): sort file search results (#1109)
This commit is contained in:
@@ -63,4 +63,32 @@ describe('directory RPC handlers', () => {
|
||||
expect(link?.type).toBe('other')
|
||||
expect(link?.size).toBeUndefined()
|
||||
})
|
||||
|
||||
it('returns metadata for a batch of searched files', async () => {
|
||||
const response = await rpc.handleRequest({
|
||||
method: 'session-test:statFiles',
|
||||
params: JSON.stringify({ paths: ['README.md', 'src/index.ts', 'missing.txt'] })
|
||||
})
|
||||
const parsed = JSON.parse(response) as {
|
||||
success: boolean
|
||||
entries?: Array<{ path: string; size?: number; modified?: number }>
|
||||
}
|
||||
|
||||
expect(parsed.success).toBe(true)
|
||||
expect(parsed.entries).toHaveLength(3)
|
||||
expect(parsed.entries?.[0]).toMatchObject({ path: 'README.md', size: 6 })
|
||||
expect(parsed.entries?.[0]?.modified).toBeTypeOf('number')
|
||||
expect(parsed.entries?.[2]).toEqual({ path: 'missing.txt' })
|
||||
})
|
||||
|
||||
it('rejects stat paths outside the session working directory', async () => {
|
||||
const response = await rpc.handleRequest({
|
||||
method: 'session-test:statFiles',
|
||||
params: JSON.stringify({ paths: ['../outside.txt'] })
|
||||
})
|
||||
const parsed = JSON.parse(response) as { success: boolean; error?: string }
|
||||
|
||||
expect(parsed.success).toBe(false)
|
||||
expect(parsed.error).toContain('outside the working directory')
|
||||
})
|
||||
})
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
import { logger } from '@/ui/logger'
|
||||
import { readdir, stat } from 'fs/promises'
|
||||
import { basename, join, resolve } from 'path'
|
||||
import type { DirectoryEntry, ListDirectoryResponse } from '@hapi/protocol/apiTypes'
|
||||
import type { DirectoryEntry, ListDirectoryResponse, StatFilesResponse } from '@hapi/protocol/apiTypes'
|
||||
import { RPC_METHODS } from '@hapi/protocol/rpcMethods'
|
||||
import type { RpcHandlerManager } from '@/api/rpc/RpcHandlerManager'
|
||||
import { validatePath } from '../pathSecurity'
|
||||
@@ -16,6 +16,10 @@ interface GetDirectoryTreeRequest {
|
||||
maxDepth: number
|
||||
}
|
||||
|
||||
interface StatFilesRequest {
|
||||
paths: string[]
|
||||
}
|
||||
|
||||
interface TreeNode {
|
||||
name: string
|
||||
path: string
|
||||
@@ -93,6 +97,35 @@ export function registerDirectoryHandlers(rpcHandlerManager: RpcHandlerManager,
|
||||
}
|
||||
})
|
||||
|
||||
rpcHandlerManager.registerHandler<StatFilesRequest, StatFilesResponse>(RPC_METHODS.StatFiles, async (data) => {
|
||||
if (!Array.isArray(data.paths) || data.paths.length > 500) {
|
||||
return rpcError('Invalid file paths')
|
||||
}
|
||||
|
||||
for (const path of data.paths) {
|
||||
const validation = validatePath(path, workingDirectory)
|
||||
if (!validation.valid) {
|
||||
return rpcError(validation.error ?? 'Invalid file path')
|
||||
}
|
||||
}
|
||||
|
||||
const entries = await Promise.all(data.paths.map(async (path) => {
|
||||
try {
|
||||
const stats = await stat(resolve(workingDirectory, path))
|
||||
return {
|
||||
path,
|
||||
size: stats.size,
|
||||
modified: stats.mtime.getTime()
|
||||
}
|
||||
} catch (error) {
|
||||
logger.debug(`Failed to stat ${path}:`, error)
|
||||
return { path }
|
||||
}
|
||||
}))
|
||||
|
||||
return { success: true, entries }
|
||||
})
|
||||
|
||||
rpcHandlerManager.registerHandler<GetDirectoryTreeRequest, GetDirectoryTreeResponse>(RPC_METHODS.GetDirectoryTree, async (data) => {
|
||||
logger.debug('Get directory tree request:', data.path, 'maxDepth:', data.maxDepth)
|
||||
|
||||
|
||||
Reference in New Issue
Block a user