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");
});
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 () => {

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 {
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

8
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;

10
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 () => {

Loading…
Cancel
Save