* Index a single file
(relativePath: string)
| 2144 | * Index a single file |
| 2145 | */ |
| 2146 | async indexFile(relativePath: string): Promise<ExtractionResult> { |
| 2147 | // Indexing read: follow in-root symlinks (the `../` guard still applies), #935. |
| 2148 | const fullPath = validatePathWithinRoot(this.rootDir, relativePath, { allowSymlinkEscape: true }); |
| 2149 | |
| 2150 | if (!fullPath) { |
| 2151 | return { |
| 2152 | nodes: [], |
| 2153 | edges: [], |
| 2154 | unresolvedReferences: [], |
| 2155 | errors: [{ message: `Path traversal blocked: ${relativePath}`, filePath: relativePath, severity: 'error', code: 'path_traversal' }], |
| 2156 | durationMs: 0, |
| 2157 | }; |
| 2158 | } |
| 2159 | |
| 2160 | // Read file content and stats |
| 2161 | let content: string; |
| 2162 | let stats: fs.Stats; |
| 2163 | try { |
| 2164 | stats = await fsp.stat(fullPath); |
| 2165 | content = await fsp.readFile(fullPath, 'utf-8'); |
| 2166 | } catch (error) { |
| 2167 | return { |
| 2168 | nodes: [], |
| 2169 | edges: [], |
| 2170 | unresolvedReferences: [], |
| 2171 | errors: [ |
| 2172 | { |
| 2173 | message: `Failed to read file: ${error instanceof Error ? error.message : String(error)}`, |
| 2174 | filePath: relativePath, |
| 2175 | severity: 'error', |
| 2176 | code: 'read_error', |
| 2177 | }, |
| 2178 | ], |
| 2179 | durationMs: 0, |
| 2180 | }; |
| 2181 | } |
| 2182 | |
| 2183 | return this.indexFileWithContent(relativePath, content, stats); |
| 2184 | } |
| 2185 | |
| 2186 | /** |
| 2187 | * Index a single file with pre-read content and stats. |
no test coverage detected