diff --git a/client/src/lib/api.ts b/client/src/lib/api.ts index f2855d9..ec55c07 100644 --- a/client/src/lib/api.ts +++ b/client/src/lib/api.ts @@ -1,5 +1,3 @@ -import { usePipelineStore } from "../store/usePipelineStore"; - // In dev: talk to Vite dev server proxy at /api // In prod: use the real backend URL from VITE_API_BASE (e.g. https://api.autodeploy.app) const DEFAULT_API_BASE = @@ -7,6 +5,7 @@ const DEFAULT_API_BASE = export const BASE = import.meta.env.VITE_API_BASE || DEFAULT_API_BASE; +import { usePipelineStore } from "../store/usePipelineStore"; // SERVER_BASE is the same as BASE but without trailing /api, // so we can call /mcp and /auth directly. @@ -519,7 +518,11 @@ export const api = { pipelineStore?.repoFullName || (pipelineStore as any)?.result?.repo; const selectedBranch = branch || (pipelineStore as any)?.selectedBranch || "main"; - const yaml = (pipelineStore as any)?.result?.generated_yaml; + const yaml = + fromCallerYaml || + (pipelineStore as any)?.result?.generated_yaml || + ""; + const environment = env || (pipelineStore as any)?.environment || "dev"; const providerFinal = provider || (pipelineStore as any)?.provider || "aws"; @@ -545,6 +548,9 @@ export const api = { }; console.log("[Deploy] Final payload:", payload); +if (!repoFullName) throw new Error("startDeploy: missing repoFullName"); +if (!selectedBranch) throw new Error("startDeploy: missing branch"); +if (!yaml || yaml.trim().length === 0) throw new Error("startDeploy: missing yaml"); const res = await fetch(`${SERVER_BASE}/mcp/v1/pipeline_commit`, { method: "POST", diff --git a/client/src/pages/ConfigurePage.tsx b/client/src/pages/ConfigurePage.tsx index b676f57..692d47a 100644 --- a/client/src/pages/ConfigurePage.tsx +++ b/client/src/pages/ConfigurePage.tsx @@ -70,7 +70,15 @@ export default function ConfigurePage() { alert("Pick a repo + branch on the Connect page first."); return; } - await regenerate({ repo, branch }); + + await regenerate({ + repo, + branch, + template, + provider, + stages, + options, + }); }; const handleOpenPr = async () => { @@ -95,6 +103,36 @@ export default function ConfigurePage() { const trimmed = chatInput.trim(); if (!trimmed) return; + // --- Sync AI intent with pipeline stages BEFORE sending to backend --- + // The AI is a planner, not an authority. UI state must be updated first. + const lower = trimmed.toLowerCase(); + + // Reset to defaults first + let nextStages: Array<"build" | "test" | "deploy"> = ["build", "test", "deploy"]; + + if (lower.includes("just build") || lower.includes("only build")) { + nextStages = ["build"]; + } else if ( + lower.includes("build and test") || + (lower.includes("build") && lower.includes("test") && !lower.includes("deploy")) + ) { + nextStages = ["build", "test"]; + } else if ( + lower.includes("no deploy") || + lower.includes("without deploy") + ) { + nextStages = ["build", "test"]; + } + + // Apply stage changes to the pipeline store + (["build", "test", "deploy"] as const).forEach((stage) => { + const shouldEnable = nextStages.includes(stage); + const isEnabled = stages.includes(stage); + if (shouldEnable !== isEnabled) { + toggleStage(stage); + } + }); + if (!repo || !branch) { alert( "Pick a repo + branch on the Connect page first so I can give better suggestions." @@ -114,7 +152,7 @@ export default function ConfigurePage() { template, provider, branch, - stages, + stages: nextStages, options, }; @@ -159,9 +197,8 @@ export default function ConfigurePage() { pipelineName, branch, provider, - stages, - // Keep a copy of the current options in wizard context so follow-up prompts - // can reference the selected provider identity (AWS role / GCP service account). + // 🔒 Never override stages from backend / metadata + stages: pipelineSnapshot.stages, options, } as any); } @@ -249,8 +286,8 @@ export default function ConfigurePage() { className="rounded-md border border-white/25 bg-white px-3 py-2 text-sm text-slate-900 placeholder-slate-500" > - - + + Pick the closest match to your repo; the MCP backend refines it. @@ -296,54 +333,99 @@ export default function ConfigurePage() { - {/* Node version + commands */} + {/* Runtime version + commands */}
- + {/* Node.js version: only show for node_app AND build stage enabled */} + {template === "node_app" && stages.includes("build") && ( + + )} - + {/* Install command: only show if build stage enabled */} + {stages.includes("build") && ( + + )} - + {/* Test command: only show if test stage enabled */} + {stages.includes("test") && ( + + )} - + {/* Build command: only show if build stage enabled */} + {stages.includes("build") && ( + + )}
- {provider === "aws" && ( + {provider === "aws" && stages.includes("deploy") && ( + <> + + + )} {provider === "gcp" && ( diff --git a/client/src/pages/DashboardPage.tsx b/client/src/pages/DashboardPage.tsx index 317539e..9995e98 100644 --- a/client/src/pages/DashboardPage.tsx +++ b/client/src/pages/DashboardPage.tsx @@ -38,6 +38,12 @@ export default function DashboardPage() { const repoFullName = result?.repo || repo || ""; const branchName = (result as any)?.branch || selectedBranch || "main"; + const environment = cfg.env || "dev"; + const workflowFile = + (result as any)?.pipeline_name || `${environment}-deploy.yml`; + const workflowPath = workflowFile.startsWith(".github/workflows/") + ? workflowFile + : `.github/workflows/${workflowFile}`; const [versions, setVersions] = useState([]); const [loadingHistory, setLoadingHistory] = useState(false); @@ -48,6 +54,12 @@ export default function DashboardPage() { const [editingYaml, setEditingYaml] = useState(false); const [draftYaml, setDraftYaml] = useState(result?.generated_yaml ?? ""); + const currentYaml = (result?.generated_yaml ?? draftYaml ?? "").trim(); + const canCommitYaml = currentYaml.length > 0; + // 🔑 Single source of truth for the currently active YAML + +// const canCommitYaml = +// (editingYaml ? draftYaml : (result?.generated_yaml ?? draftYaml))?.trim(); useEffect(() => { if (!editingYaml) { @@ -67,6 +79,7 @@ export default function DashboardPage() { const rows = await api.getPipelineHistory({ repoFullName, branch: branchName, + path: workflowPath, limit: 20, }); if (!cancelled) { @@ -88,57 +101,79 @@ export default function DashboardPage() { cancelled = true; }; // eslint-disable-next-line react-hooks/exhaustive-deps - }, [repoFullName, branchName]); - - async function handleRollback(version: PipelineVersion) { - if (!version?.id) return; - const confirmMsg = `Rollback ${repoFullName}@${branchName} to version created at ${formatDate( - version.created_at - )}?`; - if (!window.confirm(confirmMsg)) return; - - setRollbackBusy(true); - try { - const data = await api.rollbackPipeline(version.id); - console.log("[Dashboard] Rollback response:", data); - alert("Rollback queued successfully."); - - const rows = await api.getPipelineHistory({ - repoFullName, - branch, - limit: 20, - }); - setVersions(rows); - } catch (err: any) { - console.error("[Dashboard] rollbackPipeline failed:", err); - alert(err.message || "Rollback failed"); - } finally { - setRollbackBusy(false); - } - } + }, [repoFullName, branchName, workflowPath]); - function handleCommitClick() { - const repoFullNameLocal = result?.repo || repo; - const yaml = result?.generated_yaml; - const branchLocal = (result as any)?.branch || branchName || "main"; - const environment = cfg.env || "dev"; - const provider = "aws"; - const path = `.github/workflows/${environment}-deploy.yml`; - - if (!repoFullNameLocal || !yaml) { - alert("Missing repo or YAML — generate a pipeline first."); - return; - } +async function handleRollback(version: PipelineVersion) { + if (!version?.id) return; + + const confirmMsg = `Rollback ${repoFullName}@${branchName} to version created at ${formatDate( + version.created_at + )}?`; + if (!window.confirm(confirmMsg)) return; + + setRollbackBusy(true); + try { + // 👇 THIS is where the rollback happens + const data = await api.rollbackPipeline(version.id); - startDeploy({ - repoFullName: repoFullNameLocal, - branch: branchLocal, - env: environment, - yaml, - provider, - path, + // 👇 SHOW REAL OUTPUT (GitHub commit URL) + alert( + `Rollback committed ✅\n${ + data?.github?.commit?.html_url ?? "OK" + }` + ); + + // 👇 Update Current Pipeline YAML in UI + setResultYaml(version.yaml); + setEditingYaml(false); + + // 👇 Refresh history list + const rows = await api.getPipelineHistory({ + repoFullName, + branch: branchName, + path: workflowPath, + limit: 20, }); + setVersions(rows); + setSelectedVersion(rows[0] ?? null); + + } catch (err: any) { + console.error("[Dashboard] rollbackPipeline failed:", err); + alert(err.message || "Rollback failed"); + } finally { + setRollbackBusy(false); } +} + + + +async function handleCommitClick() { + const repoFullNameLocal = result?.repo || repo; + const yaml = currentYaml; + + const branchLocal = (result as any)?.branch || branchName || "main"; + const provider = "aws"; + const path = workflowPath; + + if (!repoFullNameLocal || !yaml) { + alert("Missing repo or YAML — generate a pipeline first."); + return; + } + + const res = await startDeploy({ + repoFullName: repoFullNameLocal, + branch: branchLocal, + env: environment, + yaml, + provider, + path, + }); + + // backend response you showed: res.data.commit.html_url + const url = res?.data?.commit?.html_url; + alert(url ? `Committed ✅\n${url}` : "Committed ✅"); +} + return (
@@ -183,26 +218,28 @@ export default function DashboardPage() {
-                      {result?.generated_yaml ?? "No pipeline generated yet."}
+                      {currentYaml || "No pipeline generated yet."}
+
                     
)} - +
+ size="sm" + disabled={running || !repoFullName || !canCommitYaml} + onClick={handleCommitClick} +> + {running ? "Committing…" : "Commit to GitHub"} + + {running && ( )} - {result?.generated_yaml && ( + {currentYaml && ( <>