mirror of
https://github.com/RooVetGit/Roo-Code.git
synced 2026-08-28 05:27:24 +00:00
fix: create parent directories early in write_to_file to prevent ENOENT errors (#9640)
This commit is contained in:
parent
867a5c79f9
commit
4cdec7db8d
2 changed files with 62 additions and 5 deletions
|
|
@ -7,7 +7,7 @@ import { Task } from "../task/Task"
|
|||
import { ClineSayTool } from "../../shared/ExtensionMessage"
|
||||
import { formatResponse } from "../prompts/responses"
|
||||
import { RecordSource } from "../context-tracking/FileContextTrackerTypes"
|
||||
import { fileExistsAtPath } from "../../utils/fs"
|
||||
import { fileExistsAtPath, createDirectoriesForFile } from "../../utils/fs"
|
||||
import { stripLineNumbers, everyLineHasLineNumbers } from "../../integrations/misc/extract-text"
|
||||
import { getReadablePath } from "../../utils/path"
|
||||
import { isPathOutsideWorkspace } from "../../utils/pathUtils"
|
||||
|
|
@ -70,15 +70,21 @@ export class WriteToFileTool extends BaseTool<"write_to_file"> {
|
|||
const isWriteProtected = task.rooProtectedController?.isWriteProtected(relPath) || false
|
||||
|
||||
let fileExists: boolean
|
||||
const absolutePath = path.resolve(task.cwd, relPath)
|
||||
|
||||
if (task.diffViewProvider.editType !== undefined) {
|
||||
fileExists = task.diffViewProvider.editType === "modify"
|
||||
} else {
|
||||
const absolutePath = path.resolve(task.cwd, relPath)
|
||||
fileExists = await fileExistsAtPath(absolutePath)
|
||||
task.diffViewProvider.editType = fileExists ? "modify" : "create"
|
||||
}
|
||||
|
||||
// Create parent directories early for new files to prevent ENOENT errors
|
||||
// in subsequent operations (e.g., diffViewProvider.open, fs.readFile)
|
||||
if (!fileExists) {
|
||||
await createDirectoriesForFile(absolutePath)
|
||||
}
|
||||
|
||||
if (newContent.startsWith("```")) {
|
||||
newContent = newContent.split("\n").slice(1).join("\n")
|
||||
}
|
||||
|
|
@ -307,16 +313,23 @@ export class WriteToFileTool extends BaseTool<"write_to_file"> {
|
|||
}
|
||||
|
||||
let fileExists: boolean
|
||||
const absolutePath = path.resolve(task.cwd, relPath)
|
||||
|
||||
if (task.diffViewProvider.editType !== undefined) {
|
||||
fileExists = task.diffViewProvider.editType === "modify"
|
||||
} else {
|
||||
const absolutePath = path.resolve(task.cwd, relPath)
|
||||
fileExists = await fileExistsAtPath(absolutePath)
|
||||
task.diffViewProvider.editType = fileExists ? "modify" : "create"
|
||||
}
|
||||
|
||||
// Create parent directories early for new files to prevent ENOENT errors
|
||||
// in subsequent operations (e.g., diffViewProvider.open)
|
||||
if (!fileExists) {
|
||||
await createDirectoriesForFile(absolutePath)
|
||||
}
|
||||
|
||||
const isWriteProtected = task.rooProtectedController?.isWriteProtected(relPath) || false
|
||||
const fullPath = path.resolve(task.cwd, relPath)
|
||||
const fullPath = absolutePath
|
||||
const isOutsideWorkspace = isPathOutsideWorkspace(fullPath)
|
||||
|
||||
const sharedMessageProps: ClineSayTool = {
|
||||
|
|
|
|||
|
|
@ -2,7 +2,7 @@ import * as path from "path"
|
|||
|
||||
import type { MockedFunction } from "vitest"
|
||||
|
||||
import { fileExistsAtPath } from "../../../utils/fs"
|
||||
import { fileExistsAtPath, createDirectoriesForFile } from "../../../utils/fs"
|
||||
import { detectCodeOmission } from "../../../integrations/editor/detect-omission"
|
||||
import { isPathOutsideWorkspace } from "../../../utils/pathUtils"
|
||||
import { getReadablePath } from "../../../utils/path"
|
||||
|
|
@ -29,6 +29,7 @@ vi.mock("delay", () => ({
|
|||
|
||||
vi.mock("../../../utils/fs", () => ({
|
||||
fileExistsAtPath: vi.fn().mockResolvedValue(false),
|
||||
createDirectoriesForFile: vi.fn().mockResolvedValue([]),
|
||||
}))
|
||||
|
||||
vi.mock("../../prompts/responses", () => ({
|
||||
|
|
@ -101,6 +102,7 @@ describe("writeToFileTool", () => {
|
|||
|
||||
// Mocked functions with correct types
|
||||
const mockedFileExistsAtPath = fileExistsAtPath as MockedFunction<typeof fileExistsAtPath>
|
||||
const mockedCreateDirectoriesForFile = createDirectoriesForFile as MockedFunction<typeof createDirectoriesForFile>
|
||||
const mockedDetectCodeOmission = detectCodeOmission as MockedFunction<typeof detectCodeOmission>
|
||||
const mockedIsPathOutsideWorkspace = isPathOutsideWorkspace as MockedFunction<typeof isPathOutsideWorkspace>
|
||||
const mockedGetReadablePath = getReadablePath as MockedFunction<typeof getReadablePath>
|
||||
|
|
@ -276,6 +278,48 @@ describe("writeToFileTool", () => {
|
|||
})
|
||||
})
|
||||
|
||||
describe("directory creation for new files", () => {
|
||||
it.skipIf(process.platform === "win32")(
|
||||
"creates parent directories early when file does not exist (execute)",
|
||||
async () => {
|
||||
await executeWriteFileTool({}, { fileExists: false })
|
||||
|
||||
expect(mockedCreateDirectoriesForFile).toHaveBeenCalledWith(absoluteFilePath)
|
||||
},
|
||||
)
|
||||
|
||||
it.skipIf(process.platform === "win32")(
|
||||
"creates parent directories early when file does not exist (partial)",
|
||||
async () => {
|
||||
await executeWriteFileTool({}, { fileExists: false, isPartial: true })
|
||||
|
||||
expect(mockedCreateDirectoriesForFile).toHaveBeenCalledWith(absoluteFilePath)
|
||||
},
|
||||
)
|
||||
|
||||
it("does not create directories when file exists", async () => {
|
||||
await executeWriteFileTool({}, { fileExists: true })
|
||||
|
||||
expect(mockedCreateDirectoriesForFile).not.toHaveBeenCalled()
|
||||
})
|
||||
|
||||
it("does not create directories when editType is cached as modify", async () => {
|
||||
mockCline.diffViewProvider.editType = "modify"
|
||||
|
||||
await executeWriteFileTool({})
|
||||
|
||||
expect(mockedCreateDirectoriesForFile).not.toHaveBeenCalled()
|
||||
})
|
||||
|
||||
it.skipIf(process.platform === "win32")("creates directories when editType is cached as create", async () => {
|
||||
mockCline.diffViewProvider.editType = "create"
|
||||
|
||||
await executeWriteFileTool({})
|
||||
|
||||
expect(mockedCreateDirectoriesForFile).toHaveBeenCalledWith(absoluteFilePath)
|
||||
})
|
||||
})
|
||||
|
||||
describe("content preprocessing", () => {
|
||||
it("removes markdown code block markers from content", async () => {
|
||||
await executeWriteFileTool({ content: testContentWithMarkdown })
|
||||
|
|
|
|||
Loading…
Add table
Reference in a new issue