(filePath: string, abortSignal?: AbortSignal)
| 427 | } |
| 428 | |
| 429 | private async statViaExec(filePath: string, abortSignal?: AbortSignal): Promise<FileStat> { |
| 430 | // -L follows symlinks so symlinked paths report the target's type |
| 431 | const stream = await this.exec(`stat -L -c '%s %Y %F' ${this.quoteForContainer(filePath)}`, { |
| 432 | cwd: this.getContainerBasePath(), |
| 433 | timeout: 10, |
| 434 | abortSignal, |
| 435 | }); |
| 436 | |
| 437 | const [stdout, stderr, exitCode] = await Promise.all([ |
| 438 | streamToString(stream.stdout), |
| 439 | streamToString(stream.stderr), |
| 440 | stream.exitCode, |
| 441 | ]); |
| 442 | |
| 443 | if (exitCode !== 0) { |
| 444 | throw new RuntimeError(`Failed to stat ${filePath}: ${stderr}`, "file_io"); |
| 445 | } |
| 446 | |
| 447 | const parts = stdout.trim().split(" "); |
| 448 | if (parts.length < 3) { |
| 449 | throw new RuntimeError(`Failed to parse stat output for ${filePath}: ${stdout}`, "file_io"); |
| 450 | } |
| 451 | |
| 452 | const size = parseInt(parts[0], 10); |
| 453 | const mtime = parseInt(parts[1], 10); |
| 454 | const fileType = parts.slice(2).join(" "); |
| 455 | |
| 456 | return { |
| 457 | size, |
| 458 | modifiedTime: new Date(mtime * 1000), |
| 459 | isDirectory: fileType === "directory", |
| 460 | }; |
| 461 | } |
| 462 | private mapHostPathToContainer(hostPath: string): string | null { |
| 463 | if (!this.remoteWorkspaceFolder || !this.currentWorkspacePath) return null; |
| 464 |
no test coverage detected