From f34100559e04342f3223f9c35252b315d39d1222 Mon Sep 17 00:00:00 2001 From: shrimbly Date: Sat, 28 Mar 2026 10:59:23 +1300 Subject: [PATCH] feat: add /api/list-workflows route to enumerate workflow directories Scans subdirectories of a given parent path for valid workflow JSON files, returning name, path, node count, and last modified time. Co-Authored-By: Claude Opus 4.6 --- src/app/api/list-workflows/route.ts | 82 +++++++++++++++++++++++++++++ 1 file changed, 82 insertions(+) create mode 100644 src/app/api/list-workflows/route.ts diff --git a/src/app/api/list-workflows/route.ts b/src/app/api/list-workflows/route.ts new file mode 100644 index 00000000..0f19b9e6 --- /dev/null +++ b/src/app/api/list-workflows/route.ts @@ -0,0 +1,82 @@ +import { NextRequest, NextResponse } from "next/server"; +import * as fs from "fs/promises"; +import * as path from "path"; +import { validateWorkflowPath } from "@/utils/pathValidation"; + +interface WorkflowListEntry { + name: string; + directoryPath: string; + nodeCount: number; + lastModified: number; +} + +export async function GET(request: NextRequest) { + const parentPath = request.nextUrl.searchParams.get("path"); + + if (!parentPath) { + return NextResponse.json( + { success: false, error: "Path parameter required" }, + { status: 400 } + ); + } + + const pathValidation = validateWorkflowPath(parentPath); + if (!pathValidation.valid) { + return NextResponse.json( + { success: false, error: pathValidation.error }, + { status: 400 } + ); + } + + try { + const entries = await fs.readdir(parentPath, { withFileTypes: true }); + const directories = entries.filter((e) => e.isDirectory()); + + const workflows: WorkflowListEntry[] = []; + + for (const dir of directories) { + const dirPath = path.join(parentPath, dir.name); + try { + const files = await fs.readdir(dirPath); + const jsonFiles = files.filter((f) => f.endsWith(".json")); + + for (const jsonFile of jsonFiles) { + try { + const filePath = path.join(dirPath, jsonFile); + const content = await fs.readFile(filePath, "utf-8"); + const parsed = JSON.parse(content); + + if (parsed.version && parsed.nodes && parsed.edges) { + const stat = await fs.stat(filePath); + workflows.push({ + name: parsed.name || dir.name, + directoryPath: dirPath, + nodeCount: parsed.nodes.length, + lastModified: stat.mtimeMs, + }); + break; // Use first valid workflow file per directory + } + } catch { + continue; + } + } + } catch { + // Skip directories we can't read + continue; + } + } + + // Sort by most recently modified first + workflows.sort((a, b) => b.lastModified - a.lastModified); + + return NextResponse.json({ success: true, workflows }); + } catch (error) { + return NextResponse.json( + { + success: false, + error: error instanceof Error ? error.message : "Failed to list workflows", + }, + { status: 500 } + ); + } +}