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.
Files changed (62) hide show
  1. package/dist/cli.js +19 -0
  2. package/dist/cli.js.map +1 -1
  3. package/dist/dev-environment.d.ts.map +1 -1
  4. package/dist/dev-environment.js +11 -0
  5. package/dist/dev-environment.js.map +1 -1
  6. package/dist/utils/log-filename.d.ts.map +1 -1
  7. package/dist/utils/log-filename.js +8 -3
  8. package/dist/utils/log-filename.js.map +1 -1
  9. package/mcp-server/app/mcp/route.ts +81 -7
  10. package/mcp-server/app/mcp/tools.ts +33 -3
  11. package/mcp-server/next-env.d.ts +1 -1
  12. package/mcp-server/package.json +0 -12
  13. package/package.json +6 -7
  14. package/mcp-server/.next/build/chunks/[root-of-the-server]__25374c4f._.js +0 -500
  15. package/mcp-server/.next/build/chunks/[root-of-the-server]__25374c4f._.js.map +0 -11
  16. package/mcp-server/.next/build/chunks/[root-of-the-server]__6e020478._.js +0 -441
  17. package/mcp-server/.next/build/chunks/[root-of-the-server]__6e020478._.js.map +0 -7
  18. package/mcp-server/.next/build/chunks/[root-of-the-server]__c438ef56._.js +0 -205
  19. package/mcp-server/.next/build/chunks/[root-of-the-server]__c438ef56._.js.map +0 -8
  20. package/mcp-server/.next/build/chunks/[root-of-the-server]__c7ae8543._.js +0 -500
  21. package/mcp-server/.next/build/chunks/[root-of-the-server]__c7ae8543._.js.map +0 -11
  22. package/mcp-server/.next/build/chunks/[turbopack-node]_transforms_postcss_ts_80bff36f._.js +0 -13
  23. package/mcp-server/.next/build/chunks/[turbopack-node]_transforms_postcss_ts_80bff36f._.js.map +0 -5
  24. package/mcp-server/.next/build/chunks/[turbopack-node]_transforms_webpack-loaders_ts_c84aa21a._.js +0 -12
  25. package/mcp-server/.next/build/chunks/[turbopack-node]_transforms_webpack-loaders_ts_c84aa21a._.js.map +0 -5
  26. package/mcp-server/.next/build/chunks/[turbopack]_runtime.js +0 -770
  27. package/mcp-server/.next/build/chunks/[turbopack]_runtime.js.map +0 -10
  28. package/mcp-server/.next/build/chunks/node_modules__pnpm_806d01c0._.js +0 -6758
  29. package/mcp-server/.next/build/chunks/node_modules__pnpm_806d01c0._.js.map +0 -47
  30. package/mcp-server/.next/build/package.json +0 -1
  31. package/mcp-server/.next/build/postcss.js +0 -6
  32. package/mcp-server/.next/build/postcss.js.map +0 -5
  33. package/mcp-server/.next/build/webpack-loaders.js +0 -6
  34. package/mcp-server/.next/build/webpack-loaders.js.map +0 -5
  35. package/mcp-server/.next/package.json +0 -1
  36. package/mcp-server/app/api/auth/authorize/route.ts +0 -52
  37. package/mcp-server/app/api/auth/callback/route.ts +0 -128
  38. package/mcp-server/app/api/auth/signout/route.ts +0 -34
  39. package/mcp-server/app/api/auth/token/route.ts +0 -16
  40. package/mcp-server/app/api/cloud/check-pr/route.ts +0 -53
  41. package/mcp-server/app/api/cloud/check-pr/steps.ts +0 -458
  42. package/mcp-server/app/api/cloud/check-pr/workflow.ts +0 -109
  43. package/mcp-server/app/api/cloud/fix-workflow/health/route.ts +0 -10
  44. package/mcp-server/app/api/cloud/fix-workflow/route.ts +0 -50
  45. package/mcp-server/app/api/cloud/fix-workflow/steps.ts +0 -2091
  46. package/mcp-server/app/api/cloud/fix-workflow/workflow.ts +0 -296
  47. package/mcp-server/app/api/cloud/start-fix/route.ts +0 -192
  48. package/mcp-server/app/api/integration/webhook/route.ts +0 -290
  49. package/mcp-server/app/api/projects/[projectId]/bypass-token/route.ts +0 -48
  50. package/mcp-server/app/api/projects/branches/route.ts +0 -115
  51. package/mcp-server/app/api/projects/check-protection/route.ts +0 -33
  52. package/mcp-server/app/api/projects/route.ts +0 -97
  53. package/mcp-server/app/api/workflows/route.ts +0 -105
  54. package/mcp-server/app/auth/error/page.tsx +0 -47
  55. package/mcp-server/app/signin/page.tsx +0 -37
  56. package/mcp-server/app/workflows/[id]/report/agent-analysis.tsx +0 -7
  57. package/mcp-server/app/workflows/[id]/report/page.tsx +0 -199
  58. package/mcp-server/app/workflows/new/new-workflow-client.tsx +0 -32
  59. package/mcp-server/app/workflows/new/page.tsx +0 -13
  60. package/mcp-server/app/workflows/new-workflow-modal.tsx +0 -973
  61. package/mcp-server/app/workflows/page.tsx +0 -16
  62. 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
- }