Browse Source

fix: allow saving to new project subfolders

handoff-20260429-1057
Marquis Boyd 5 months ago
parent
commit
bb73fb5074
  1. 19
      src/app/api/workflow/__tests__/route.test.ts
  2. 38
      src/app/api/workflow/route.ts
  3. 8
      src/components/ProjectSetupModal.tsx
  4. 10
      src/components/__tests__/ProjectSetupModal.test.tsx

19
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"); expect(data.error).toBe("Path is not a directory");
}); });
it("should reject non-existent directory", async () => { it("should create non-existent directory and continue saving", async () => {
mockStat.mockRejectedValue(new Error("ENOENT")); const mockWorkflow = { nodes: [], edges: [] };
mockStat.mockRejectedValue(Object.assign(new Error("ENOENT"), { code: "ENOENT" }));
mockMkdir.mockResolvedValue(undefined);
mockWriteFile.mockResolvedValue(undefined);
const request = createMockPostRequest({ const request = createMockPostRequest({
directoryPath: "/nonexistent/dir", directoryPath: "/nonexistent/dir",
filename: "workflow", filename: "workflow",
workflow: { nodes: [], edges: [] }, workflow: mockWorkflow,
}); });
const response = await POST(request); const response = await POST(request);
const data = await response.json(); const data = await response.json();
expect(response.status).toBe(400); expect(data.success).toBe(true);
expect(data.success).toBe(false); expect(mockMkdir).toHaveBeenCalledWith("/nonexistent/dir", { recursive: true });
expect(data.error).toBe("Directory does not exist"); expect(mockWriteFile).toHaveBeenCalledWith(
"/nonexistent/dir/workflow.json",
JSON.stringify(mockWorkflow, null, 2),
"utf-8"
);
}); });
it("should continue saving even if subfolder creation fails", async () => { it("should continue saving even if subfolder creation fails", async () => {

38
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 { try {
const stats = await fs.stat(directoryPath); const stats = await fs.stat(directoryPath);
if (!stats.isDirectory()) { if (!stats.isDirectory()) {
@ -48,13 +48,35 @@ export async function POST(request: NextRequest) {
); );
} }
} catch (dirError) { } catch (dirError) {
logger.warn('file.error', 'Workflow save failed: directory does not exist', { const err = dirError as NodeJS.ErrnoException;
directoryPath, const isNotFound =
}); err?.code === "ENOENT" ||
return NextResponse.json( (typeof err?.message === "string" &&
{ success: false, error: "Directory does not exist" }, (err.message.includes("ENOENT") || err.message.includes("no such file or directory")));
{ status: 400 }
); 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 // Auto-create subfolders for inputs and generations

8
src/components/ProjectSetupModal.tsx

@ -242,13 +242,7 @@ export function ProjectSetupModal({
); );
const result = await response.json(); const result = await response.json();
if (!result.exists) { if (result.exists && !result.isDirectory) {
setError("Project directory does not exist");
setIsValidating(false);
return;
}
if (!result.isDirectory) {
setError("Project path is not a directory"); setError("Project path is not a directory");
setIsValidating(false); setIsValidating(false);
return; return;

10
src/components/__tests__/ProjectSetupModal.test.tsx

@ -339,7 +339,7 @@ describe("ProjectSetupModal", () => {
expect(onSave).not.toHaveBeenCalled(); 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) => { mockFetch.mockImplementation((url: string) => {
if (url === "/api/env-status") { if (url === "/api/env-status") {
return Promise.resolve({ return Promise.resolve({
@ -379,9 +379,13 @@ describe("ProjectSetupModal", () => {
fireEvent.click(screen.getByText("Create")); fireEvent.click(screen.getByText("Create"));
await waitFor(() => { 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 () => { it("should show error when path is not absolute", async () => {

Loading…
Cancel
Save