dev3000 0.0.121 → 0.0.124
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/cli.js +19 -0
- package/dist/cli.js.map +1 -1
- package/dist/dev-environment.d.ts.map +1 -1
- package/dist/dev-environment.js +11 -0
- package/dist/dev-environment.js.map +1 -1
- package/dist/utils/log-filename.d.ts.map +1 -1
- package/dist/utils/log-filename.js +8 -3
- package/dist/utils/log-filename.js.map +1 -1
- package/mcp-server/app/mcp/route.ts +81 -7
- package/mcp-server/app/mcp/tools.ts +33 -3
- package/mcp-server/next-env.d.ts +1 -1
- package/mcp-server/package.json +0 -12
- package/package.json +6 -7
- package/mcp-server/.next/build/chunks/[root-of-the-server]__25374c4f._.js +0 -500
- package/mcp-server/.next/build/chunks/[root-of-the-server]__25374c4f._.js.map +0 -11
- package/mcp-server/.next/build/chunks/[root-of-the-server]__6e020478._.js +0 -441
- package/mcp-server/.next/build/chunks/[root-of-the-server]__6e020478._.js.map +0 -7
- package/mcp-server/.next/build/chunks/[root-of-the-server]__c438ef56._.js +0 -205
- package/mcp-server/.next/build/chunks/[root-of-the-server]__c438ef56._.js.map +0 -8
- package/mcp-server/.next/build/chunks/[root-of-the-server]__c7ae8543._.js +0 -500
- package/mcp-server/.next/build/chunks/[root-of-the-server]__c7ae8543._.js.map +0 -11
- package/mcp-server/.next/build/chunks/[turbopack-node]_transforms_postcss_ts_80bff36f._.js +0 -13
- package/mcp-server/.next/build/chunks/[turbopack-node]_transforms_postcss_ts_80bff36f._.js.map +0 -5
- package/mcp-server/.next/build/chunks/[turbopack-node]_transforms_webpack-loaders_ts_c84aa21a._.js +0 -12
- package/mcp-server/.next/build/chunks/[turbopack-node]_transforms_webpack-loaders_ts_c84aa21a._.js.map +0 -5
- package/mcp-server/.next/build/chunks/[turbopack]_runtime.js +0 -770
- package/mcp-server/.next/build/chunks/[turbopack]_runtime.js.map +0 -10
- package/mcp-server/.next/build/chunks/node_modules__pnpm_806d01c0._.js +0 -6758
- package/mcp-server/.next/build/chunks/node_modules__pnpm_806d01c0._.js.map +0 -47
- package/mcp-server/.next/build/package.json +0 -1
- package/mcp-server/.next/build/postcss.js +0 -6
- package/mcp-server/.next/build/postcss.js.map +0 -5
- package/mcp-server/.next/build/webpack-loaders.js +0 -6
- package/mcp-server/.next/build/webpack-loaders.js.map +0 -5
- package/mcp-server/.next/package.json +0 -1
- package/mcp-server/app/api/auth/authorize/route.ts +0 -52
- package/mcp-server/app/api/auth/callback/route.ts +0 -128
- package/mcp-server/app/api/auth/signout/route.ts +0 -34
- package/mcp-server/app/api/auth/token/route.ts +0 -16
- package/mcp-server/app/api/cloud/check-pr/route.ts +0 -53
- package/mcp-server/app/api/cloud/check-pr/steps.ts +0 -458
- package/mcp-server/app/api/cloud/check-pr/workflow.ts +0 -109
- package/mcp-server/app/api/cloud/fix-workflow/health/route.ts +0 -10
- package/mcp-server/app/api/cloud/fix-workflow/route.ts +0 -50
- package/mcp-server/app/api/cloud/fix-workflow/steps.ts +0 -2091
- package/mcp-server/app/api/cloud/fix-workflow/workflow.ts +0 -296
- package/mcp-server/app/api/cloud/start-fix/route.ts +0 -192
- package/mcp-server/app/api/integration/webhook/route.ts +0 -290
- package/mcp-server/app/api/projects/[projectId]/bypass-token/route.ts +0 -48
- package/mcp-server/app/api/projects/branches/route.ts +0 -115
- package/mcp-server/app/api/projects/check-protection/route.ts +0 -33
- package/mcp-server/app/api/projects/route.ts +0 -97
- package/mcp-server/app/api/workflows/route.ts +0 -105
- package/mcp-server/app/auth/error/page.tsx +0 -47
- package/mcp-server/app/signin/page.tsx +0 -37
- package/mcp-server/app/workflows/[id]/report/agent-analysis.tsx +0 -7
- package/mcp-server/app/workflows/[id]/report/page.tsx +0 -199
- package/mcp-server/app/workflows/new/new-workflow-client.tsx +0 -32
- package/mcp-server/app/workflows/new/page.tsx +0 -13
- package/mcp-server/app/workflows/new-workflow-modal.tsx +0 -973
- package/mcp-server/app/workflows/page.tsx +0 -16
- package/mcp-server/app/workflows/workflows-client.tsx +0 -290
|
@@ -1,973 +0,0 @@
|
|
|
1
|
-
"use client"
|
|
2
|
-
|
|
3
|
-
import { AlertCircle } from "lucide-react"
|
|
4
|
-
import Link from "next/link"
|
|
5
|
-
import { useRouter, useSearchParams } from "next/navigation"
|
|
6
|
-
import { useEffect, useId, useRef, useState } from "react"
|
|
7
|
-
import { Alert, AlertDescription, AlertTitle } from "@/components/ui/alert"
|
|
8
|
-
import { Checkbox } from "@/components/ui/checkbox"
|
|
9
|
-
import { Label } from "@/components/ui/label"
|
|
10
|
-
import { Progress } from "@/components/ui/progress"
|
|
11
|
-
import { Select, SelectContent, SelectItem, SelectTrigger, SelectValue } from "@/components/ui/select"
|
|
12
|
-
import { Spinner } from "@/components/ui/spinner"
|
|
13
|
-
|
|
14
|
-
interface Team {
|
|
15
|
-
id: string
|
|
16
|
-
slug: string
|
|
17
|
-
name: string
|
|
18
|
-
isPersonal: boolean
|
|
19
|
-
}
|
|
20
|
-
|
|
21
|
-
interface Project {
|
|
22
|
-
id: string
|
|
23
|
-
name: string
|
|
24
|
-
framework: string | null
|
|
25
|
-
link: {
|
|
26
|
-
type: string
|
|
27
|
-
repo: string
|
|
28
|
-
repoId: number
|
|
29
|
-
org: string
|
|
30
|
-
} | null
|
|
31
|
-
latestDeployments: Array<{
|
|
32
|
-
id: string
|
|
33
|
-
url: string
|
|
34
|
-
state: string
|
|
35
|
-
readyState: string
|
|
36
|
-
createdAt: number
|
|
37
|
-
gitSource: {
|
|
38
|
-
type: string
|
|
39
|
-
repoId: number
|
|
40
|
-
ref: string
|
|
41
|
-
sha: string
|
|
42
|
-
message: string
|
|
43
|
-
} | null
|
|
44
|
-
}>
|
|
45
|
-
}
|
|
46
|
-
|
|
47
|
-
interface NewWorkflowModalProps {
|
|
48
|
-
isOpen: boolean
|
|
49
|
-
onClose: () => void
|
|
50
|
-
userId: string
|
|
51
|
-
}
|
|
52
|
-
|
|
53
|
-
type WorkflowStep = "type" | "team" | "project" | "options" | "running"
|
|
54
|
-
|
|
55
|
-
export default function NewWorkflowModal({ isOpen, onClose, userId }: NewWorkflowModalProps) {
|
|
56
|
-
const router = useRouter()
|
|
57
|
-
const searchParams = useSearchParams()
|
|
58
|
-
const baseBranchId = useId()
|
|
59
|
-
const autoCreatePRId = useId()
|
|
60
|
-
const bypassTokenId = useId()
|
|
61
|
-
|
|
62
|
-
// Initialize step from URL params to avoid CLS from cascading useEffects
|
|
63
|
-
const initialStep = (() => {
|
|
64
|
-
const typeParam = searchParams.get("type")
|
|
65
|
-
const teamParam = searchParams.get("team")
|
|
66
|
-
const projectParam = searchParams.get("project")
|
|
67
|
-
|
|
68
|
-
if (!typeParam) return "type"
|
|
69
|
-
if (projectParam) return "options"
|
|
70
|
-
if (teamParam) return "project"
|
|
71
|
-
return "team"
|
|
72
|
-
})()
|
|
73
|
-
|
|
74
|
-
const [step, setStep] = useState<WorkflowStep>(initialStep)
|
|
75
|
-
const [_selectedType, setSelectedType] = useState<string>(searchParams.get("type") || "")
|
|
76
|
-
const [teams, setTeams] = useState<Team[]>([])
|
|
77
|
-
const [selectedTeam, setSelectedTeam] = useState<Team | null>(null)
|
|
78
|
-
const [projects, setProjects] = useState<Project[]>([])
|
|
79
|
-
const [selectedProject, setSelectedProject] = useState<Project | null>(null)
|
|
80
|
-
const [loadingTeams, setLoadingTeams] = useState(false)
|
|
81
|
-
const [loadingProjects, setLoadingProjects] = useState(false)
|
|
82
|
-
const [projectsError, setProjectsError] = useState<string | null>(null)
|
|
83
|
-
const [workflowStatus, setWorkflowStatus] = useState<string>("")
|
|
84
|
-
// biome-ignore lint/suspicious/noExplicitAny: API response type is dynamic
|
|
85
|
-
const [workflowResult, setWorkflowResult] = useState<any>(null)
|
|
86
|
-
const [activeRunId, setActiveRunId] = useState<string | null>(null)
|
|
87
|
-
const [sandboxUrl, setSandboxUrl] = useState<string | null>(null)
|
|
88
|
-
const [baseBranch, setBaseBranch] = useState("main")
|
|
89
|
-
const [autoCreatePR, setAutoCreatePR] = useState(true)
|
|
90
|
-
const [bypassToken, setBypassToken] = useState("")
|
|
91
|
-
const [isCheckingProtection, setIsCheckingProtection] = useState(false)
|
|
92
|
-
const [needsBypassToken, setNeedsBypassToken] = useState(false)
|
|
93
|
-
const [availableBranches, setAvailableBranches] = useState<
|
|
94
|
-
Array<{ name: string; lastDeployment: { url: string; createdAt: number } }>
|
|
95
|
-
>([])
|
|
96
|
-
const [loadingBranches, setLoadingBranches] = useState(false)
|
|
97
|
-
const [branchesError, setBranchesError] = useState(false)
|
|
98
|
-
const loadedTeamIdRef = useRef<string | null>(null)
|
|
99
|
-
|
|
100
|
-
// Restore state from URL whenever searchParams change (after initial load)
|
|
101
|
-
// This handles the case where user navigates via browser back/forward
|
|
102
|
-
useEffect(() => {
|
|
103
|
-
if (!isOpen) return
|
|
104
|
-
|
|
105
|
-
// Don't interfere with the "running" state - it's controlled by workflow execution, not URL
|
|
106
|
-
if (step === "running") return
|
|
107
|
-
|
|
108
|
-
const typeParam = searchParams.get("type")
|
|
109
|
-
const teamParam = searchParams.get("team")
|
|
110
|
-
const projectParam = searchParams.get("project")
|
|
111
|
-
|
|
112
|
-
// Only update if there's a meaningful change from current state
|
|
113
|
-
if (typeParam && typeParam !== _selectedType) {
|
|
114
|
-
setSelectedType(typeParam)
|
|
115
|
-
}
|
|
116
|
-
|
|
117
|
-
// Determine the correct step based on URL params
|
|
118
|
-
const targetStep: WorkflowStep = !typeParam ? "type" : projectParam ? "options" : teamParam ? "project" : "team"
|
|
119
|
-
|
|
120
|
-
if (targetStep !== step) {
|
|
121
|
-
setStep(targetStep)
|
|
122
|
-
}
|
|
123
|
-
|
|
124
|
-
// Reset selections if params are removed
|
|
125
|
-
if (!typeParam) {
|
|
126
|
-
setSelectedType("")
|
|
127
|
-
setSelectedTeam(null)
|
|
128
|
-
setSelectedProject(null)
|
|
129
|
-
}
|
|
130
|
-
}, [isOpen, searchParams, step, _selectedType])
|
|
131
|
-
|
|
132
|
-
// Reset modal state when closed
|
|
133
|
-
useEffect(() => {
|
|
134
|
-
if (!isOpen) {
|
|
135
|
-
setStep("type")
|
|
136
|
-
setSelectedType("")
|
|
137
|
-
setSelectedTeam(null)
|
|
138
|
-
setSelectedProject(null)
|
|
139
|
-
setProjects([])
|
|
140
|
-
setTeams([])
|
|
141
|
-
setWorkflowStatus("")
|
|
142
|
-
setWorkflowResult(null)
|
|
143
|
-
setActiveRunId(null)
|
|
144
|
-
setSandboxUrl(null)
|
|
145
|
-
setBaseBranch("main")
|
|
146
|
-
setAutoCreatePR(true)
|
|
147
|
-
setBypassToken("")
|
|
148
|
-
setNeedsBypassToken(false)
|
|
149
|
-
setProjectsError(null)
|
|
150
|
-
setAvailableBranches([])
|
|
151
|
-
setLoadingBranches(false)
|
|
152
|
-
setBranchesError(false)
|
|
153
|
-
loadedTeamIdRef.current = null
|
|
154
|
-
router.replace("/workflows", { scroll: false })
|
|
155
|
-
}
|
|
156
|
-
}, [isOpen, router])
|
|
157
|
-
|
|
158
|
-
// Load teams when needed
|
|
159
|
-
// biome-ignore lint/correctness/useExhaustiveDependencies: loadTeams is stable and doesn't need to be a dependency
|
|
160
|
-
useEffect(() => {
|
|
161
|
-
if (["team", "project", "options"].includes(step) && teams.length === 0 && !loadingTeams) {
|
|
162
|
-
loadTeams()
|
|
163
|
-
}
|
|
164
|
-
}, [step, teams.length, loadingTeams])
|
|
165
|
-
|
|
166
|
-
// Restore team from URL once teams are loaded
|
|
167
|
-
useEffect(() => {
|
|
168
|
-
const teamParam = searchParams.get("team")
|
|
169
|
-
if (teamParam && teams.length > 0) {
|
|
170
|
-
// Update team if URL param differs from currently selected team
|
|
171
|
-
if (!selectedTeam || selectedTeam.id !== teamParam) {
|
|
172
|
-
const team = teams.find((t) => t.id === teamParam)
|
|
173
|
-
if (team) {
|
|
174
|
-
setSelectedTeam(team)
|
|
175
|
-
}
|
|
176
|
-
}
|
|
177
|
-
}
|
|
178
|
-
}, [teams, searchParams, selectedTeam])
|
|
179
|
-
|
|
180
|
-
// Load projects when team selected
|
|
181
|
-
// biome-ignore lint/correctness/useExhaustiveDependencies: loadProjects is stable and doesn't need to be a dependency
|
|
182
|
-
useEffect(() => {
|
|
183
|
-
if (selectedTeam && !loadingProjects && loadedTeamIdRef.current !== selectedTeam.id) {
|
|
184
|
-
loadedTeamIdRef.current = selectedTeam.id
|
|
185
|
-
loadProjects(selectedTeam)
|
|
186
|
-
}
|
|
187
|
-
}, [selectedTeam, loadingProjects])
|
|
188
|
-
|
|
189
|
-
// Load branches when project and team are selected and on options step
|
|
190
|
-
// biome-ignore lint/correctness/useExhaustiveDependencies: loadBranches is stable and doesn't need to be a dependency
|
|
191
|
-
useEffect(() => {
|
|
192
|
-
if (
|
|
193
|
-
selectedProject &&
|
|
194
|
-
selectedTeam &&
|
|
195
|
-
step === "options" &&
|
|
196
|
-
availableBranches.length === 0 &&
|
|
197
|
-
!loadingBranches &&
|
|
198
|
-
!branchesError
|
|
199
|
-
) {
|
|
200
|
-
loadBranches(selectedProject, selectedTeam)
|
|
201
|
-
}
|
|
202
|
-
}, [selectedProject, selectedTeam, step, availableBranches.length, loadingBranches, branchesError])
|
|
203
|
-
|
|
204
|
-
// Poll workflow status when running
|
|
205
|
-
// biome-ignore lint/correctness/useExhaustiveDependencies: selectedProject is used for matching but shouldn't re-trigger polling
|
|
206
|
-
useEffect(() => {
|
|
207
|
-
if (!userId || step !== "running") return
|
|
208
|
-
|
|
209
|
-
const pollStatus = async () => {
|
|
210
|
-
try {
|
|
211
|
-
// Use production API for consistent status
|
|
212
|
-
const response = await fetch(`https://d3k-mcp.vercel.sh/api/workflows?userId=${userId}`)
|
|
213
|
-
if (!response.ok) return
|
|
214
|
-
|
|
215
|
-
const data = await response.json()
|
|
216
|
-
if (!data.success || !data.runs) return
|
|
217
|
-
|
|
218
|
-
// Find the run - either by activeRunId or by matching project + running status
|
|
219
|
-
// biome-ignore lint/suspicious/noExplicitAny: API response type is dynamic
|
|
220
|
-
let run: any = null
|
|
221
|
-
if (activeRunId) {
|
|
222
|
-
// biome-ignore lint/suspicious/noExplicitAny: API response type is dynamic
|
|
223
|
-
run = data.runs.find((r: any) => r.id === activeRunId)
|
|
224
|
-
} else if (selectedProject) {
|
|
225
|
-
// Find the most recent running workflow for this project
|
|
226
|
-
// biome-ignore lint/suspicious/noExplicitAny: API response type is dynamic
|
|
227
|
-
run = data.runs.find((r: any) => r.projectName === selectedProject.name && r.status === "running")
|
|
228
|
-
if (run) {
|
|
229
|
-
setActiveRunId(run.id)
|
|
230
|
-
}
|
|
231
|
-
}
|
|
232
|
-
if (!run) return
|
|
233
|
-
|
|
234
|
-
// Update status from real backend data
|
|
235
|
-
if (run.currentStep) {
|
|
236
|
-
setWorkflowStatus(run.currentStep)
|
|
237
|
-
}
|
|
238
|
-
if (run.sandboxUrl) {
|
|
239
|
-
setSandboxUrl(run.sandboxUrl)
|
|
240
|
-
}
|
|
241
|
-
|
|
242
|
-
// Check for completion
|
|
243
|
-
if (run.status === "done") {
|
|
244
|
-
setWorkflowStatus("Workflow completed successfully!")
|
|
245
|
-
setWorkflowResult({
|
|
246
|
-
success: true,
|
|
247
|
-
blobUrl: run.reportBlobUrl,
|
|
248
|
-
runId: run.id,
|
|
249
|
-
pr: run.prUrl ? { prUrl: run.prUrl } : null
|
|
250
|
-
})
|
|
251
|
-
} else if (run.status === "failure") {
|
|
252
|
-
setWorkflowStatus(`Workflow failed: ${run.error || "Unknown error"}`)
|
|
253
|
-
}
|
|
254
|
-
} catch (error) {
|
|
255
|
-
console.error("[Poll Status] Error:", error)
|
|
256
|
-
}
|
|
257
|
-
}
|
|
258
|
-
|
|
259
|
-
// Poll every 3 seconds
|
|
260
|
-
const interval = setInterval(pollStatus, 3000)
|
|
261
|
-
// Also poll immediately
|
|
262
|
-
pollStatus()
|
|
263
|
-
|
|
264
|
-
return () => clearInterval(interval)
|
|
265
|
-
}, [activeRunId, userId, step])
|
|
266
|
-
|
|
267
|
-
// Check if deployment is protected when project is selected and on options step
|
|
268
|
-
useEffect(() => {
|
|
269
|
-
async function checkDeploymentProtection() {
|
|
270
|
-
console.log("[Bypass Token] useEffect triggered - step:", step, "selectedProject:", selectedProject?.name)
|
|
271
|
-
|
|
272
|
-
if (!selectedProject) {
|
|
273
|
-
console.log("[Bypass Token] No selected project, skipping check")
|
|
274
|
-
return
|
|
275
|
-
}
|
|
276
|
-
|
|
277
|
-
if (step !== "options") {
|
|
278
|
-
console.log("[Bypass Token] Not on options step, skipping check")
|
|
279
|
-
return
|
|
280
|
-
}
|
|
281
|
-
|
|
282
|
-
// Load stored token for this project from localStorage
|
|
283
|
-
const storageKey = `d3k_bypass_token_${selectedProject.id}`
|
|
284
|
-
const storedToken = localStorage.getItem(storageKey)
|
|
285
|
-
if (storedToken) {
|
|
286
|
-
console.log("[Bypass Token] Found stored token for project", selectedProject.id)
|
|
287
|
-
setBypassToken(storedToken)
|
|
288
|
-
}
|
|
289
|
-
|
|
290
|
-
const latestDeployment = selectedProject.latestDeployments[0]
|
|
291
|
-
if (!latestDeployment) {
|
|
292
|
-
console.log("[Bypass Token] No latest deployment, skipping check")
|
|
293
|
-
return
|
|
294
|
-
}
|
|
295
|
-
|
|
296
|
-
setIsCheckingProtection(true)
|
|
297
|
-
console.log("[Bypass Token] Checking deployment protection...")
|
|
298
|
-
try {
|
|
299
|
-
const devUrl = `https://${latestDeployment.url}`
|
|
300
|
-
console.log("[Bypass Token] Checking URL:", devUrl)
|
|
301
|
-
|
|
302
|
-
// Use server-side API route to avoid CORS issues
|
|
303
|
-
const response = await fetch("/api/projects/check-protection", {
|
|
304
|
-
method: "POST",
|
|
305
|
-
headers: { "Content-Type": "application/json" },
|
|
306
|
-
body: JSON.stringify({ url: devUrl })
|
|
307
|
-
})
|
|
308
|
-
|
|
309
|
-
const data = await response.json()
|
|
310
|
-
console.log("[Bypass Token] Protection check result:", data)
|
|
311
|
-
|
|
312
|
-
setNeedsBypassToken(data.isProtected)
|
|
313
|
-
} catch (error) {
|
|
314
|
-
console.error("[Bypass Token] Failed to check deployment protection:", error)
|
|
315
|
-
// Assume not protected on error
|
|
316
|
-
setNeedsBypassToken(false)
|
|
317
|
-
} finally {
|
|
318
|
-
setIsCheckingProtection(false)
|
|
319
|
-
}
|
|
320
|
-
}
|
|
321
|
-
|
|
322
|
-
checkDeploymentProtection()
|
|
323
|
-
}, [selectedProject, step])
|
|
324
|
-
|
|
325
|
-
// Restore project from URL once projects are loaded
|
|
326
|
-
useEffect(() => {
|
|
327
|
-
const projectParam = searchParams.get("project")
|
|
328
|
-
if (projectParam && projects.length > 0) {
|
|
329
|
-
// Update if no project selected OR if the URL project differs from selected
|
|
330
|
-
if (!selectedProject || selectedProject.id !== projectParam) {
|
|
331
|
-
const project = projects.find((p) => p.id === projectParam)
|
|
332
|
-
if (project) {
|
|
333
|
-
setSelectedProject(project)
|
|
334
|
-
}
|
|
335
|
-
}
|
|
336
|
-
}
|
|
337
|
-
}, [projects, searchParams, selectedProject])
|
|
338
|
-
|
|
339
|
-
async function loadTeams() {
|
|
340
|
-
setLoadingTeams(true)
|
|
341
|
-
try {
|
|
342
|
-
const response = await fetch("/api/teams")
|
|
343
|
-
const data = await response.json()
|
|
344
|
-
if (data.success) {
|
|
345
|
-
// Sort teams alphabetically by name (personal account first if present, then alphabetically)
|
|
346
|
-
const sortedTeams = [...data.teams].sort((a, b) => {
|
|
347
|
-
// Personal accounts first
|
|
348
|
-
if (a.isPersonal && !b.isPersonal) return -1
|
|
349
|
-
if (!a.isPersonal && b.isPersonal) return 1
|
|
350
|
-
// Then alphabetically by name (case-insensitive)
|
|
351
|
-
return a.name.localeCompare(b.name, undefined, { sensitivity: "base" })
|
|
352
|
-
})
|
|
353
|
-
setTeams(sortedTeams)
|
|
354
|
-
}
|
|
355
|
-
} catch (error) {
|
|
356
|
-
console.error("Failed to load teams:", error)
|
|
357
|
-
} finally {
|
|
358
|
-
setLoadingTeams(false)
|
|
359
|
-
}
|
|
360
|
-
}
|
|
361
|
-
|
|
362
|
-
async function loadBranches(project: Project, team: Team) {
|
|
363
|
-
setLoadingBranches(true)
|
|
364
|
-
setBranchesError(false)
|
|
365
|
-
try {
|
|
366
|
-
const url = team.isPersonal
|
|
367
|
-
? `/api/projects/branches?projectId=${project.id}`
|
|
368
|
-
: `/api/projects/branches?projectId=${project.id}&teamId=${team.id}`
|
|
369
|
-
console.log("Fetching branches from:", url)
|
|
370
|
-
const response = await fetch(url)
|
|
371
|
-
const data = await response.json()
|
|
372
|
-
console.log("Branches response:", data)
|
|
373
|
-
|
|
374
|
-
if (data.success && data.branches) {
|
|
375
|
-
setAvailableBranches(data.branches)
|
|
376
|
-
// If current baseBranch is not in the list, reset to first available or "main"
|
|
377
|
-
if (data.branches.length > 0 && !data.branches.some((b: { name: string }) => b.name === baseBranch)) {
|
|
378
|
-
const mainBranch = data.branches.find((b: { name: string }) => b.name === "main")
|
|
379
|
-
setBaseBranch(mainBranch?.name || data.branches[0].name)
|
|
380
|
-
}
|
|
381
|
-
} else {
|
|
382
|
-
// API call succeeded but returned error (like 403)
|
|
383
|
-
setBranchesError(true)
|
|
384
|
-
console.warn("Failed to fetch branches:", data.error)
|
|
385
|
-
}
|
|
386
|
-
} catch (error) {
|
|
387
|
-
console.error("Failed to load branches:", error)
|
|
388
|
-
setBranchesError(true)
|
|
389
|
-
} finally {
|
|
390
|
-
setLoadingBranches(false)
|
|
391
|
-
}
|
|
392
|
-
}
|
|
393
|
-
|
|
394
|
-
async function loadProjects(team: Team) {
|
|
395
|
-
setLoadingProjects(true)
|
|
396
|
-
setProjectsError(null)
|
|
397
|
-
try {
|
|
398
|
-
const url = team.isPersonal ? "/api/projects" : `/api/projects?teamId=${team.id}`
|
|
399
|
-
console.log("Fetching projects from:", url)
|
|
400
|
-
const response = await fetch(url)
|
|
401
|
-
const data = await response.json()
|
|
402
|
-
console.log("Projects response:", data)
|
|
403
|
-
|
|
404
|
-
if (data.success) {
|
|
405
|
-
setProjects(data.projects)
|
|
406
|
-
if (data.projects.length === 0) {
|
|
407
|
-
setProjectsError("No projects found for this account")
|
|
408
|
-
}
|
|
409
|
-
} else {
|
|
410
|
-
const errorMsg = `Failed to fetch projects: ${data.error || "Unknown error"}`
|
|
411
|
-
console.error(errorMsg)
|
|
412
|
-
setProjectsError(errorMsg)
|
|
413
|
-
}
|
|
414
|
-
} catch (error) {
|
|
415
|
-
const errorMsg = `Failed to load projects: ${error instanceof Error ? error.message : String(error)}`
|
|
416
|
-
console.error(errorMsg, error)
|
|
417
|
-
setProjectsError(errorMsg)
|
|
418
|
-
} finally {
|
|
419
|
-
setLoadingProjects(false)
|
|
420
|
-
}
|
|
421
|
-
}
|
|
422
|
-
|
|
423
|
-
async function startWorkflow() {
|
|
424
|
-
console.log("[Start Workflow] Function called")
|
|
425
|
-
console.log("[Start Workflow] selectedProject:", selectedProject)
|
|
426
|
-
console.log("[Start Workflow] selectedTeam:", selectedTeam)
|
|
427
|
-
|
|
428
|
-
if (!selectedProject || !selectedTeam) {
|
|
429
|
-
console.log("[Start Workflow] Missing project or team, returning")
|
|
430
|
-
return
|
|
431
|
-
}
|
|
432
|
-
|
|
433
|
-
setStep("running")
|
|
434
|
-
setWorkflowStatus("Starting workflow...")
|
|
435
|
-
|
|
436
|
-
try {
|
|
437
|
-
// Get the latest deployment URL
|
|
438
|
-
console.log("[Start Workflow] latestDeployments:", selectedProject.latestDeployments)
|
|
439
|
-
const latestDeployment = selectedProject.latestDeployments[0]
|
|
440
|
-
if (!latestDeployment) {
|
|
441
|
-
throw new Error("No deployments found for this project")
|
|
442
|
-
}
|
|
443
|
-
|
|
444
|
-
const devUrl = `https://${latestDeployment.url}`
|
|
445
|
-
console.log("[Start Workflow] devUrl:", devUrl)
|
|
446
|
-
|
|
447
|
-
// Extract repo info from project link
|
|
448
|
-
let repoOwner: string | undefined
|
|
449
|
-
let repoName: string | undefined
|
|
450
|
-
|
|
451
|
-
if (selectedProject.link?.org && selectedProject.link?.repo) {
|
|
452
|
-
repoOwner = selectedProject.link.org
|
|
453
|
-
repoName = selectedProject.link.repo
|
|
454
|
-
}
|
|
455
|
-
|
|
456
|
-
// biome-ignore lint/suspicious/noExplicitAny: Request body type depends on conditional fields
|
|
457
|
-
const body: any = {
|
|
458
|
-
devUrl,
|
|
459
|
-
projectName: selectedProject.name,
|
|
460
|
-
userId,
|
|
461
|
-
bypassToken
|
|
462
|
-
}
|
|
463
|
-
|
|
464
|
-
// If we have repo info, pass it for sandbox creation
|
|
465
|
-
// Use the deployment's git SHA if available, otherwise fall back to baseBranch
|
|
466
|
-
if (repoOwner && repoName) {
|
|
467
|
-
body.repoUrl = `https://github.com/${repoOwner}/${repoName}`
|
|
468
|
-
body.repoBranch = latestDeployment.gitSource?.sha || baseBranch || "main"
|
|
469
|
-
console.log(
|
|
470
|
-
`[Start Workflow] Using git reference: ${body.repoBranch} (${latestDeployment.gitSource?.sha ? "SHA from deployment" : "branch name"})`
|
|
471
|
-
)
|
|
472
|
-
}
|
|
473
|
-
|
|
474
|
-
if (autoCreatePR && repoOwner && repoName) {
|
|
475
|
-
body.repoOwner = repoOwner
|
|
476
|
-
body.repoName = repoName
|
|
477
|
-
body.baseBranch = baseBranch
|
|
478
|
-
}
|
|
479
|
-
|
|
480
|
-
// Initial status - will be updated by polling
|
|
481
|
-
setWorkflowStatus("Starting workflow...")
|
|
482
|
-
|
|
483
|
-
// Always use production API endpoint for workflow execution
|
|
484
|
-
const apiBaseUrl = "https://d3k-mcp.vercel.sh"
|
|
485
|
-
const apiUrl = `${apiBaseUrl}/api/cloud/start-fix`
|
|
486
|
-
|
|
487
|
-
console.log("[Start Workflow] API URL:", apiUrl)
|
|
488
|
-
console.log("[Start Workflow] Request body:", body)
|
|
489
|
-
|
|
490
|
-
// Create an AbortController for timeout handling
|
|
491
|
-
const controller = new AbortController()
|
|
492
|
-
const timeoutId = setTimeout(() => controller.abort(), 10 * 60 * 1000) // 10 minute timeout (matches server maxDuration)
|
|
493
|
-
|
|
494
|
-
try {
|
|
495
|
-
console.log("[Start Workflow] Making fetch request...")
|
|
496
|
-
console.log("[Start Workflow] About to stringify body...")
|
|
497
|
-
const bodyString = JSON.stringify(body)
|
|
498
|
-
console.log("[Start Workflow] Body stringified successfully, length:", bodyString.length)
|
|
499
|
-
console.log("[Start Workflow] Calling fetch with URL:", apiUrl)
|
|
500
|
-
|
|
501
|
-
// Get access token for Authorization header (needed for cross-origin requests)
|
|
502
|
-
const tokenResponse = await fetch("/api/auth/token")
|
|
503
|
-
const tokenData = await tokenResponse.json()
|
|
504
|
-
const accessToken = tokenData.accessToken
|
|
505
|
-
|
|
506
|
-
const headers: HeadersInit = { "Content-Type": "application/json" }
|
|
507
|
-
if (accessToken) {
|
|
508
|
-
// Always include Authorization header for production API
|
|
509
|
-
headers.Authorization = `Bearer ${accessToken}`
|
|
510
|
-
}
|
|
511
|
-
|
|
512
|
-
const response = await fetch(apiUrl, {
|
|
513
|
-
method: "POST",
|
|
514
|
-
headers,
|
|
515
|
-
body: bodyString,
|
|
516
|
-
credentials: "include",
|
|
517
|
-
signal: controller.signal
|
|
518
|
-
})
|
|
519
|
-
|
|
520
|
-
console.log("[Start Workflow] Fetch completed, status:", response.status)
|
|
521
|
-
|
|
522
|
-
clearTimeout(timeoutId)
|
|
523
|
-
|
|
524
|
-
if (!response.ok) {
|
|
525
|
-
const errorText = await response.text()
|
|
526
|
-
throw new Error(`API returned ${response.status}: ${errorText}`)
|
|
527
|
-
}
|
|
528
|
-
|
|
529
|
-
const result = await response.json()
|
|
530
|
-
|
|
531
|
-
// Set activeRunId for polling - let polling handle status updates
|
|
532
|
-
if (result.runId) {
|
|
533
|
-
setActiveRunId(result.runId)
|
|
534
|
-
}
|
|
535
|
-
|
|
536
|
-
// Don't immediately show completion - let polling verify the workflow status
|
|
537
|
-
// The API may return success=true but polling should confirm the backend state
|
|
538
|
-
// This prevents showing "completed" while the workflow is still running
|
|
539
|
-
if (!result.success) {
|
|
540
|
-
setWorkflowStatus(`Workflow failed: ${result.error}`)
|
|
541
|
-
}
|
|
542
|
-
// If success, let polling update the status when it confirms the run is "done"
|
|
543
|
-
} catch (fetchError) {
|
|
544
|
-
clearTimeout(timeoutId)
|
|
545
|
-
if (fetchError instanceof Error && fetchError.name === "AbortError") {
|
|
546
|
-
throw new Error("Workflow timed out after 10 minutes")
|
|
547
|
-
}
|
|
548
|
-
throw fetchError
|
|
549
|
-
}
|
|
550
|
-
} catch (error) {
|
|
551
|
-
console.error("[Start Workflow] OUTER ERROR caught:", error)
|
|
552
|
-
console.error("[Start Workflow] Error type:", error?.constructor?.name)
|
|
553
|
-
console.error("[Start Workflow] Error stack:", error instanceof Error ? error.stack : "no stack")
|
|
554
|
-
|
|
555
|
-
let errorMessage = error instanceof Error ? error.message : String(error)
|
|
556
|
-
|
|
557
|
-
if (error instanceof TypeError && errorMessage === "Failed to fetch") {
|
|
558
|
-
errorMessage =
|
|
559
|
-
"Network error: Unable to connect to API. This might be a CORS issue, network problem, or Content Security Policy blocking the request."
|
|
560
|
-
}
|
|
561
|
-
|
|
562
|
-
setWorkflowStatus(`Error: ${errorMessage}`)
|
|
563
|
-
}
|
|
564
|
-
}
|
|
565
|
-
|
|
566
|
-
if (!isOpen) return null
|
|
567
|
-
|
|
568
|
-
return (
|
|
569
|
-
<div className="fixed inset-0 bg-black bg-opacity-50 flex items-center justify-center z-50">
|
|
570
|
-
<div className="bg-white rounded-lg shadow-xl max-w-2xl w-full mx-4 max-h-[90vh] overflow-y-auto">
|
|
571
|
-
<div className="p-6">
|
|
572
|
-
<div className="flex justify-between items-start mb-6">
|
|
573
|
-
<h2 className="text-2xl font-bold text-gray-900">New d3k Workflow</h2>
|
|
574
|
-
<button
|
|
575
|
-
type="button"
|
|
576
|
-
onClick={onClose}
|
|
577
|
-
className="text-gray-400 hover:text-gray-600"
|
|
578
|
-
aria-label="Close modal"
|
|
579
|
-
>
|
|
580
|
-
<svg className="w-6 h-6" fill="none" stroke="currentColor" viewBox="0 0 24 24">
|
|
581
|
-
<path strokeLinecap="round" strokeLinejoin="round" strokeWidth={2} d="M6 18L18 6M6 6l12 12" />
|
|
582
|
-
</svg>
|
|
583
|
-
</button>
|
|
584
|
-
</div>
|
|
585
|
-
|
|
586
|
-
{/* Progress indicator */}
|
|
587
|
-
<div className="mb-8">
|
|
588
|
-
<div className="flex items-center">
|
|
589
|
-
{["type", "team", "project", "options", "running"].map((s, index) => (
|
|
590
|
-
<div key={s} className="flex items-center flex-1">
|
|
591
|
-
<div
|
|
592
|
-
className={`w-8 h-8 rounded-full flex items-center justify-center ${
|
|
593
|
-
step === s
|
|
594
|
-
? "bg-blue-600 text-white"
|
|
595
|
-
: ["type", "team", "project", "options", "running"].indexOf(step) >
|
|
596
|
-
["type", "team", "project", "options", "running"].indexOf(s)
|
|
597
|
-
? "bg-green-600 text-white"
|
|
598
|
-
: "bg-gray-200 text-gray-600"
|
|
599
|
-
}`}
|
|
600
|
-
>
|
|
601
|
-
{index + 1}
|
|
602
|
-
</div>
|
|
603
|
-
{index < 4 && (
|
|
604
|
-
<div className="flex-1 mx-2">
|
|
605
|
-
<Progress
|
|
606
|
-
value={["type", "team", "project", "options", "running"].indexOf(step) > index ? 100 : 0}
|
|
607
|
-
className="h-1"
|
|
608
|
-
/>
|
|
609
|
-
</div>
|
|
610
|
-
)}
|
|
611
|
-
</div>
|
|
612
|
-
))}
|
|
613
|
-
</div>
|
|
614
|
-
<div className="flex items-center mt-2">
|
|
615
|
-
{["Type", "Team", "Project", "Options", "Run"].map((label, index) => (
|
|
616
|
-
<div key={label} className="flex items-center flex-1">
|
|
617
|
-
<span className="text-xs text-gray-600 w-8 text-center">{label}</span>
|
|
618
|
-
{index < 4 && <div className="flex-1 mx-2" />}
|
|
619
|
-
</div>
|
|
620
|
-
))}
|
|
621
|
-
</div>
|
|
622
|
-
</div>
|
|
623
|
-
|
|
624
|
-
{/* Step 1: Select Workflow Type */}
|
|
625
|
-
{step === "type" && (
|
|
626
|
-
<div>
|
|
627
|
-
<h3 className="text-lg font-semibold mb-4">Select Workflow Type</h3>
|
|
628
|
-
<div className="space-y-3">
|
|
629
|
-
<Link
|
|
630
|
-
href="/workflows/new?type=cloud-fix"
|
|
631
|
-
className="block w-full p-4 border-2 border-gray-300 rounded-lg hover:border-blue-500 hover:bg-blue-50 text-left transition-colors"
|
|
632
|
-
>
|
|
633
|
-
<div className="font-semibold">CLS Detection & Fix</div>
|
|
634
|
-
<div className="text-sm text-gray-600 mt-1">
|
|
635
|
-
Analyze deployment logs for errors and generate fix proposals
|
|
636
|
-
</div>
|
|
637
|
-
</Link>
|
|
638
|
-
<Link
|
|
639
|
-
href="/workflows/new?type=next-16-migration"
|
|
640
|
-
className="block w-full p-4 border-2 border-gray-300 rounded-lg hover:border-blue-500 hover:bg-blue-50 text-left transition-colors"
|
|
641
|
-
>
|
|
642
|
-
<div className="font-semibold">Next.js 16 Migration</div>
|
|
643
|
-
<div className="text-sm text-gray-600 mt-1">
|
|
644
|
-
Upgrade your project to Next.js 16 with automated codemods and fixes
|
|
645
|
-
</div>
|
|
646
|
-
</Link>
|
|
647
|
-
</div>
|
|
648
|
-
{/* Reserve space for Back link to prevent CLS */}
|
|
649
|
-
<div className="mt-4 h-10" aria-hidden="true" />
|
|
650
|
-
</div>
|
|
651
|
-
)}
|
|
652
|
-
|
|
653
|
-
{/* Step 2: Select Team */}
|
|
654
|
-
{step === "team" && (
|
|
655
|
-
<div>
|
|
656
|
-
<h3 className="text-lg font-semibold mb-4">Select Team</h3>
|
|
657
|
-
{loadingTeams ? (
|
|
658
|
-
<div className="text-center py-8 text-gray-500">Loading teams...</div>
|
|
659
|
-
) : (
|
|
660
|
-
<div className="space-y-2 max-h-96 overflow-y-auto">
|
|
661
|
-
{teams.map((team) => (
|
|
662
|
-
<Link
|
|
663
|
-
key={team.id}
|
|
664
|
-
href={`/workflows/new?type=${_selectedType}&team=${team.id}`}
|
|
665
|
-
className="block w-full p-4 border-2 border-gray-300 rounded-lg hover:border-blue-500 hover:bg-blue-50 text-left transition-colors"
|
|
666
|
-
>
|
|
667
|
-
<div className="font-semibold">{team.name}</div>
|
|
668
|
-
<div className="text-sm text-gray-600">
|
|
669
|
-
{team.isPersonal ? "Personal Account" : "Team"} • {team.id}
|
|
670
|
-
</div>
|
|
671
|
-
</Link>
|
|
672
|
-
))}
|
|
673
|
-
</div>
|
|
674
|
-
)}
|
|
675
|
-
<Link href="/workflows/new" className="mt-4 inline-block px-4 py-2 text-gray-600 hover:text-gray-800">
|
|
676
|
-
← Back
|
|
677
|
-
</Link>
|
|
678
|
-
</div>
|
|
679
|
-
)}
|
|
680
|
-
|
|
681
|
-
{/* Step 3: Select Project */}
|
|
682
|
-
{step === "project" && (
|
|
683
|
-
<div>
|
|
684
|
-
<h3 className="text-lg font-semibold mb-4">Select Project</h3>
|
|
685
|
-
{selectedTeam && (
|
|
686
|
-
<div className="mb-4 text-sm text-gray-600">
|
|
687
|
-
Team: <span className="font-semibold">{selectedTeam.name}</span>
|
|
688
|
-
</div>
|
|
689
|
-
)}
|
|
690
|
-
{projectsError && (
|
|
691
|
-
<Alert variant="destructive" className="mb-4">
|
|
692
|
-
<AlertCircle className="h-4 w-4" />
|
|
693
|
-
<AlertTitle>Error</AlertTitle>
|
|
694
|
-
<AlertDescription>{projectsError}</AlertDescription>
|
|
695
|
-
</Alert>
|
|
696
|
-
)}
|
|
697
|
-
{loadingProjects ? (
|
|
698
|
-
<div className="text-center py-8 text-gray-500">Loading projects...</div>
|
|
699
|
-
) : projects.length === 0 ? (
|
|
700
|
-
<div className="text-center py-8 text-gray-500">
|
|
701
|
-
{projectsError ? "Unable to load projects" : "No projects found"}
|
|
702
|
-
</div>
|
|
703
|
-
) : (
|
|
704
|
-
<div className="space-y-2 max-h-96 overflow-y-auto">
|
|
705
|
-
{projects.map((project) => (
|
|
706
|
-
<Link
|
|
707
|
-
key={project.id}
|
|
708
|
-
href={`/workflows/new?type=${_selectedType}&team=${selectedTeam?.id}&project=${project.id}`}
|
|
709
|
-
className="block w-full p-4 border-2 border-gray-300 rounded-lg hover:border-blue-500 hover:bg-blue-50 text-left transition-colors"
|
|
710
|
-
>
|
|
711
|
-
<div className="font-semibold">{project.name}</div>
|
|
712
|
-
<div className="text-sm text-gray-600 mt-1">
|
|
713
|
-
{project.framework && <span className="mr-2">Framework: {project.framework}</span>}
|
|
714
|
-
{project.latestDeployments[0] && <span>Latest: {project.latestDeployments[0].url}</span>}
|
|
715
|
-
</div>
|
|
716
|
-
</Link>
|
|
717
|
-
))}
|
|
718
|
-
</div>
|
|
719
|
-
)}
|
|
720
|
-
<Link
|
|
721
|
-
href={`/workflows/new?type=${_selectedType}`}
|
|
722
|
-
className="mt-4 inline-block px-4 py-2 text-gray-600 hover:text-gray-800"
|
|
723
|
-
>
|
|
724
|
-
← Back
|
|
725
|
-
</Link>
|
|
726
|
-
</div>
|
|
727
|
-
)}
|
|
728
|
-
|
|
729
|
-
{/* Step 4: Configure Options */}
|
|
730
|
-
{step === "options" && (
|
|
731
|
-
<div>
|
|
732
|
-
<h3 className="text-lg font-semibold mb-4">Configure Options</h3>
|
|
733
|
-
{!selectedProject ? (
|
|
734
|
-
<div className="text-center py-8 text-gray-500">
|
|
735
|
-
<Spinner className="mx-auto mb-2" />
|
|
736
|
-
Loading project details...
|
|
737
|
-
</div>
|
|
738
|
-
) : (
|
|
739
|
-
<>
|
|
740
|
-
<div className="mb-6 p-4 bg-gray-50 rounded-lg">
|
|
741
|
-
<div className="text-sm text-gray-600 mb-2">Selected Project:</div>
|
|
742
|
-
<div className="font-semibold">{selectedProject.name}</div>
|
|
743
|
-
</div>
|
|
744
|
-
<div className="space-y-4">
|
|
745
|
-
<div className="flex items-center space-x-2">
|
|
746
|
-
<Checkbox
|
|
747
|
-
id={autoCreatePRId}
|
|
748
|
-
checked={autoCreatePR}
|
|
749
|
-
onCheckedChange={(checked) => setAutoCreatePR(checked === true)}
|
|
750
|
-
/>
|
|
751
|
-
<Label htmlFor={autoCreatePRId} className="text-sm font-normal cursor-pointer">
|
|
752
|
-
Automatically create GitHub PR with fixes
|
|
753
|
-
</Label>
|
|
754
|
-
</div>
|
|
755
|
-
{autoCreatePR && selectedProject?.link?.repo && (
|
|
756
|
-
<div>
|
|
757
|
-
<Label htmlFor={baseBranchId} className="mb-2">
|
|
758
|
-
Base Branch
|
|
759
|
-
</Label>
|
|
760
|
-
{loadingBranches ? (
|
|
761
|
-
<div className="text-sm text-gray-500 py-2">Loading branches...</div>
|
|
762
|
-
) : availableBranches.length > 0 ? (
|
|
763
|
-
<>
|
|
764
|
-
<Select value={baseBranch} onValueChange={setBaseBranch}>
|
|
765
|
-
<SelectTrigger id={baseBranchId} className="w-full">
|
|
766
|
-
<SelectValue placeholder="Select a branch" />
|
|
767
|
-
</SelectTrigger>
|
|
768
|
-
<SelectContent>
|
|
769
|
-
{availableBranches.map((branch) => (
|
|
770
|
-
<SelectItem key={branch.name} value={branch.name}>
|
|
771
|
-
{branch.name} (deployed{" "}
|
|
772
|
-
{new Date(branch.lastDeployment.createdAt).toLocaleDateString()})
|
|
773
|
-
</SelectItem>
|
|
774
|
-
))}
|
|
775
|
-
</SelectContent>
|
|
776
|
-
</Select>
|
|
777
|
-
<p className="text-xs text-muted-foreground mt-1">
|
|
778
|
-
Showing branches with recent deployments (last {availableBranches.length} branch
|
|
779
|
-
{availableBranches.length !== 1 ? "es" : ""})
|
|
780
|
-
</p>
|
|
781
|
-
</>
|
|
782
|
-
) : (
|
|
783
|
-
<>
|
|
784
|
-
<input
|
|
785
|
-
type="text"
|
|
786
|
-
id={baseBranchId}
|
|
787
|
-
value={baseBranch}
|
|
788
|
-
onChange={(e) => setBaseBranch(e.target.value)}
|
|
789
|
-
className="w-full px-3 py-2 border border-gray-300 rounded-md"
|
|
790
|
-
placeholder="main"
|
|
791
|
-
/>
|
|
792
|
-
{branchesError && (
|
|
793
|
-
<p className="text-xs text-amber-600 mt-1">
|
|
794
|
-
Unable to load branches automatically. Please enter the branch name manually.
|
|
795
|
-
</p>
|
|
796
|
-
)}
|
|
797
|
-
</>
|
|
798
|
-
)}
|
|
799
|
-
</div>
|
|
800
|
-
)}
|
|
801
|
-
{autoCreatePR && !selectedProject?.link?.repo && (
|
|
802
|
-
<div className="text-sm text-amber-600">
|
|
803
|
-
This project is not connected to a GitHub repository. PRs cannot be created automatically.
|
|
804
|
-
</div>
|
|
805
|
-
)}
|
|
806
|
-
{isCheckingProtection && (
|
|
807
|
-
<div className="text-sm text-gray-500">Checking deployment protection...</div>
|
|
808
|
-
)}
|
|
809
|
-
{needsBypassToken && (
|
|
810
|
-
<div>
|
|
811
|
-
<label htmlFor={bypassTokenId} className="block text-sm font-medium text-gray-700 mb-1">
|
|
812
|
-
Deployment Protection Bypass Token
|
|
813
|
-
<span className="text-red-500 ml-1">*</span>
|
|
814
|
-
</label>
|
|
815
|
-
<input
|
|
816
|
-
type="text"
|
|
817
|
-
id={bypassTokenId}
|
|
818
|
-
value={bypassToken}
|
|
819
|
-
onChange={(e) => {
|
|
820
|
-
const newToken = e.target.value
|
|
821
|
-
setBypassToken(newToken)
|
|
822
|
-
// Save to localStorage for this project
|
|
823
|
-
if (selectedProject && newToken) {
|
|
824
|
-
const storageKey = `d3k_bypass_token_${selectedProject.id}`
|
|
825
|
-
localStorage.setItem(storageKey, newToken)
|
|
826
|
-
console.log("[Bypass Token] Saved token to localStorage for project", selectedProject.id)
|
|
827
|
-
}
|
|
828
|
-
}}
|
|
829
|
-
className="w-full px-3 py-2 border border-gray-300 rounded-md font-mono text-sm"
|
|
830
|
-
placeholder="Enter your 32-character bypass token"
|
|
831
|
-
required
|
|
832
|
-
/>
|
|
833
|
-
<p className="mt-1 text-xs text-gray-500">
|
|
834
|
-
This deployment is protected. Get your bypass token from{" "}
|
|
835
|
-
{selectedTeam && selectedProject ? (
|
|
836
|
-
<a
|
|
837
|
-
href={`https://vercel.com/${selectedTeam.slug}/${selectedProject.name}/settings/deployment-protection#protection-bypass-for-automation`}
|
|
838
|
-
target="_blank"
|
|
839
|
-
rel="noopener noreferrer"
|
|
840
|
-
className="text-blue-600 hover:underline"
|
|
841
|
-
>
|
|
842
|
-
Project Settings → Deployment Protection
|
|
843
|
-
</a>
|
|
844
|
-
) : (
|
|
845
|
-
<a
|
|
846
|
-
href="https://vercel.com/docs/deployment-protection/methods-to-bypass-deployment-protection/protection-bypass-automation"
|
|
847
|
-
target="_blank"
|
|
848
|
-
rel="noopener noreferrer"
|
|
849
|
-
className="text-blue-600 hover:underline"
|
|
850
|
-
>
|
|
851
|
-
Vercel Dashboard → Project Settings → Deployment Protection
|
|
852
|
-
</a>
|
|
853
|
-
)}
|
|
854
|
-
</p>
|
|
855
|
-
</div>
|
|
856
|
-
)}
|
|
857
|
-
</div>
|
|
858
|
-
<div className="flex gap-3 mt-6">
|
|
859
|
-
<Link
|
|
860
|
-
href={`/workflows/new?type=${_selectedType}&team=${selectedTeam?.id}`}
|
|
861
|
-
className="px-4 py-2 text-gray-600 hover:text-gray-800"
|
|
862
|
-
>
|
|
863
|
-
← Back
|
|
864
|
-
</Link>
|
|
865
|
-
<button
|
|
866
|
-
type="button"
|
|
867
|
-
onClick={startWorkflow}
|
|
868
|
-
disabled={needsBypassToken && !bypassToken}
|
|
869
|
-
className="flex-1 px-6 py-2 bg-blue-600 text-white rounded-md hover:bg-blue-700 disabled:opacity-50 disabled:cursor-not-allowed"
|
|
870
|
-
>
|
|
871
|
-
Start Workflow
|
|
872
|
-
</button>
|
|
873
|
-
</div>
|
|
874
|
-
</>
|
|
875
|
-
)}
|
|
876
|
-
</div>
|
|
877
|
-
)}
|
|
878
|
-
|
|
879
|
-
{/* Step 5: Running / Results */}
|
|
880
|
-
{step === "running" && (
|
|
881
|
-
<div>
|
|
882
|
-
<h3 className="text-lg font-semibold mb-4">Workflow Status</h3>
|
|
883
|
-
<div className="space-y-4">
|
|
884
|
-
<Alert
|
|
885
|
-
variant={
|
|
886
|
-
workflowStatus.includes("Error") || workflowStatus.includes("failed") ? "destructive" : "default"
|
|
887
|
-
}
|
|
888
|
-
className={
|
|
889
|
-
workflowStatus.includes("Error") || workflowStatus.includes("failed")
|
|
890
|
-
? ""
|
|
891
|
-
: "bg-blue-50 border-blue-200"
|
|
892
|
-
}
|
|
893
|
-
>
|
|
894
|
-
{workflowStatus.includes("Error") || workflowStatus.includes("failed") ? (
|
|
895
|
-
<AlertCircle className="h-4 w-4" />
|
|
896
|
-
) : (
|
|
897
|
-
!workflowStatus.includes("completed successfully") && <Spinner className="h-4 w-4" />
|
|
898
|
-
)}
|
|
899
|
-
<AlertDescription
|
|
900
|
-
className={
|
|
901
|
-
workflowStatus.includes("Error") || workflowStatus.includes("failed") ? "" : "text-blue-900"
|
|
902
|
-
}
|
|
903
|
-
>
|
|
904
|
-
{workflowStatus}
|
|
905
|
-
</AlertDescription>
|
|
906
|
-
</Alert>
|
|
907
|
-
{sandboxUrl && !workflowResult && (
|
|
908
|
-
<Alert className="bg-yellow-50 border-yellow-200">
|
|
909
|
-
<AlertDescription className="text-yellow-900">
|
|
910
|
-
<span className="font-medium">Sandbox:</span>{" "}
|
|
911
|
-
<a
|
|
912
|
-
href={sandboxUrl}
|
|
913
|
-
target="_blank"
|
|
914
|
-
rel="noopener noreferrer"
|
|
915
|
-
className="text-blue-600 hover:underline font-mono text-sm"
|
|
916
|
-
>
|
|
917
|
-
{sandboxUrl}
|
|
918
|
-
</a>
|
|
919
|
-
</AlertDescription>
|
|
920
|
-
</Alert>
|
|
921
|
-
)}
|
|
922
|
-
{workflowResult && (
|
|
923
|
-
<div className="space-y-3">
|
|
924
|
-
{workflowResult.blobUrl && workflowResult.runId && (
|
|
925
|
-
<Alert className="bg-green-50 border-green-200">
|
|
926
|
-
<AlertTitle className="text-green-900">Fix Proposal Generated</AlertTitle>
|
|
927
|
-
<AlertDescription>
|
|
928
|
-
<Link
|
|
929
|
-
href={`/workflows/${workflowResult.runId}/report`}
|
|
930
|
-
className="text-primary hover:underline"
|
|
931
|
-
>
|
|
932
|
-
View Report
|
|
933
|
-
</Link>
|
|
934
|
-
</AlertDescription>
|
|
935
|
-
</Alert>
|
|
936
|
-
)}
|
|
937
|
-
{workflowResult.pr?.prUrl && (
|
|
938
|
-
<Alert className="bg-green-50 border-green-200">
|
|
939
|
-
<AlertTitle className="text-green-900">GitHub PR Created</AlertTitle>
|
|
940
|
-
<AlertDescription>
|
|
941
|
-
<a
|
|
942
|
-
href={workflowResult.pr.prUrl}
|
|
943
|
-
target="_blank"
|
|
944
|
-
rel="noopener noreferrer"
|
|
945
|
-
className="text-primary hover:underline"
|
|
946
|
-
>
|
|
947
|
-
View Pull Request
|
|
948
|
-
</a>
|
|
949
|
-
</AlertDescription>
|
|
950
|
-
</Alert>
|
|
951
|
-
)}
|
|
952
|
-
</div>
|
|
953
|
-
)}
|
|
954
|
-
{workflowResult && (
|
|
955
|
-
<button
|
|
956
|
-
type="button"
|
|
957
|
-
onClick={() => {
|
|
958
|
-
onClose()
|
|
959
|
-
router.push("/workflows")
|
|
960
|
-
}}
|
|
961
|
-
className="w-full px-6 py-2 bg-blue-600 text-white rounded-md hover:bg-blue-700"
|
|
962
|
-
>
|
|
963
|
-
Done
|
|
964
|
-
</button>
|
|
965
|
-
)}
|
|
966
|
-
</div>
|
|
967
|
-
</div>
|
|
968
|
-
)}
|
|
969
|
-
</div>
|
|
970
|
-
</div>
|
|
971
|
-
</div>
|
|
972
|
-
)
|
|
973
|
-
}
|