diff --git a/src/app/api/workflow/__tests__/route.test.ts b/src/app/api/workflow/__tests__/route.test.ts index c4b5ec5e..d61d18ea 100644 --- a/src/app/api/workflow/__tests__/route.test.ts +++ b/src/app/api/workflow/__tests__/route.test.ts @@ -183,21 +183,28 @@ describe("/api/workflow route", () => { expect(data.error).toBe("Path is not a directory"); }); - it("should reject non-existent directory", async () => { - mockStat.mockRejectedValue(new Error("ENOENT")); + it("should create non-existent directory and continue saving", async () => { + const mockWorkflow = { nodes: [], edges: [] }; + mockStat.mockRejectedValue(Object.assign(new Error("ENOENT"), { code: "ENOENT" })); + mockMkdir.mockResolvedValue(undefined); + mockWriteFile.mockResolvedValue(undefined); const request = createMockPostRequest({ directoryPath: "/nonexistent/dir", filename: "workflow", - workflow: { nodes: [], edges: [] }, + workflow: mockWorkflow, }); const response = await POST(request); const data = await response.json(); - expect(response.status).toBe(400); - expect(data.success).toBe(false); - expect(data.error).toBe("Directory does not exist"); + expect(data.success).toBe(true); + expect(mockMkdir).toHaveBeenCalledWith("/nonexistent/dir", { recursive: true }); + expect(mockWriteFile).toHaveBeenCalledWith( + "/nonexistent/dir/workflow.json", + JSON.stringify(mockWorkflow, null, 2), + "utf-8" + ); }); it("should continue saving even if subfolder creation fails", async () => { diff --git a/src/app/api/workflow/route.ts b/src/app/api/workflow/route.ts index cb8ed2d6..b95b303e 100644 --- a/src/app/api/workflow/route.ts +++ b/src/app/api/workflow/route.ts @@ -35,7 +35,7 @@ export async function POST(request: NextRequest) { ); } - // Validate directory exists + // Ensure project directory exists (supports saving into new subfolders) try { const stats = await fs.stat(directoryPath); if (!stats.isDirectory()) { @@ -48,13 +48,35 @@ export async function POST(request: NextRequest) { ); } } catch (dirError) { - logger.warn('file.error', 'Workflow save failed: directory does not exist', { - directoryPath, - }); - return NextResponse.json( - { success: false, error: "Directory does not exist" }, - { status: 400 } - ); + const err = dirError as NodeJS.ErrnoException; + const isNotFound = + err?.code === "ENOENT" || + (typeof err?.message === "string" && + (err.message.includes("ENOENT") || err.message.includes("no such file or directory"))); + + if (!isNotFound) { + logger.warn('file.error', 'Workflow save failed: directory validation error', { + directoryPath, + error: dirError instanceof Error ? dirError.message : 'Unknown error', + }); + return NextResponse.json( + { success: false, error: "Directory validation failed" }, + { status: 400 } + ); + } + + try { + await fs.mkdir(directoryPath, { recursive: true }); + } catch (mkdirError) { + logger.warn('file.error', 'Workflow save failed: could not create directory', { + directoryPath, + error: mkdirError instanceof Error ? mkdirError.message : 'Unknown error', + }); + return NextResponse.json( + { success: false, error: "Could not create directory" }, + { status: 400 } + ); + } } // Auto-create subfolders for inputs and generations diff --git a/src/components/ProjectSetupModal.tsx b/src/components/ProjectSetupModal.tsx index 4f12c64a..349a09aa 100644 --- a/src/components/ProjectSetupModal.tsx +++ b/src/components/ProjectSetupModal.tsx @@ -242,13 +242,7 @@ export function ProjectSetupModal({ ); const result = await response.json(); - if (!result.exists) { - setError("Project directory does not exist"); - setIsValidating(false); - return; - } - - if (!result.isDirectory) { + if (result.exists && !result.isDirectory) { setError("Project path is not a directory"); setIsValidating(false); return; diff --git a/src/components/__tests__/ProjectSetupModal.test.tsx b/src/components/__tests__/ProjectSetupModal.test.tsx index c0317520..64e861bd 100644 --- a/src/components/__tests__/ProjectSetupModal.test.tsx +++ b/src/components/__tests__/ProjectSetupModal.test.tsx @@ -339,7 +339,7 @@ describe("ProjectSetupModal", () => { expect(onSave).not.toHaveBeenCalled(); }); - it("should show error when directory does not exist", async () => { + it("should allow creating project in a new subfolder path", async () => { mockFetch.mockImplementation((url: string) => { if (url === "/api/env-status") { return Promise.resolve({ @@ -379,9 +379,13 @@ describe("ProjectSetupModal", () => { fireEvent.click(screen.getByText("Create")); await waitFor(() => { - expect(screen.getByText("Project directory does not exist")).toBeInTheDocument(); + expect(onSave).toHaveBeenCalledTimes(1); }); - expect(onSave).not.toHaveBeenCalled(); + expect(onSave).toHaveBeenCalledWith( + expect.any(String), + "My Project", + "/nonexistent/path" + ); }); it("should show error when path is not absolute", async () => {