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,290 +0,0 @@
|
|
|
1
|
-
/**
|
|
2
|
-
* Vercel Integration Webhook Handler
|
|
3
|
-
*
|
|
4
|
-
* This endpoint receives webhooks from Vercel when deployments are ready.
|
|
5
|
-
* It automatically triggers PR checks for preview deployments.
|
|
6
|
-
*
|
|
7
|
-
* Note: This uses Vercel's deployment metadata which includes GitHub info
|
|
8
|
-
* automatically - no GitHub token setup required!
|
|
9
|
-
*/
|
|
10
|
-
|
|
11
|
-
export async function POST(request: Request) {
|
|
12
|
-
try {
|
|
13
|
-
const payload = await request.json()
|
|
14
|
-
|
|
15
|
-
console.log("[Webhook] Received deployment event")
|
|
16
|
-
console.log(`[Webhook] Type: ${payload.type}`)
|
|
17
|
-
console.log(`[Webhook] Deployment: ${payload.deployment?.url}`)
|
|
18
|
-
|
|
19
|
-
// Only process deployment.created events for preview deployments
|
|
20
|
-
if (payload.type !== "deployment.created") {
|
|
21
|
-
return Response.json({ message: "Ignoring non-deployment event" })
|
|
22
|
-
}
|
|
23
|
-
|
|
24
|
-
const deployment = payload.deployment
|
|
25
|
-
|
|
26
|
-
// Only process preview deployments (not production)
|
|
27
|
-
if (deployment.target === "production") {
|
|
28
|
-
console.log("[Webhook] Skipping production deployment")
|
|
29
|
-
return Response.json({ message: "Skipping production deployment" })
|
|
30
|
-
}
|
|
31
|
-
|
|
32
|
-
// Check if this is a PR deployment
|
|
33
|
-
if (!deployment.meta?.githubCommitRef || !deployment.meta?.githubOrg || !deployment.meta?.githubRepo) {
|
|
34
|
-
console.log("[Webhook] Not a GitHub PR deployment")
|
|
35
|
-
return Response.json({ message: "Not a GitHub PR deployment" })
|
|
36
|
-
}
|
|
37
|
-
|
|
38
|
-
const branch = deployment.meta.githubCommitRef
|
|
39
|
-
const owner = deployment.meta.githubOrg
|
|
40
|
-
const repo = deployment.meta.githubRepo
|
|
41
|
-
const previewUrl = `https://${deployment.url}`
|
|
42
|
-
|
|
43
|
-
console.log(`[Webhook] Processing PR deployment for ${owner}/${repo}#${branch}`)
|
|
44
|
-
|
|
45
|
-
// Find the PR number for this branch
|
|
46
|
-
const prNumber = await findPRNumber(owner, repo, branch)
|
|
47
|
-
|
|
48
|
-
if (!prNumber) {
|
|
49
|
-
console.log(`[Webhook] No PR found for branch: ${branch}`)
|
|
50
|
-
return Response.json({ message: "No PR found for this branch" })
|
|
51
|
-
}
|
|
52
|
-
|
|
53
|
-
console.log(`[Webhook] Found PR #${prNumber}`)
|
|
54
|
-
|
|
55
|
-
// Fetch PR details
|
|
56
|
-
const prDetails = await fetchPRDetails(owner, repo, prNumber)
|
|
57
|
-
|
|
58
|
-
if (!prDetails) {
|
|
59
|
-
console.log(`[Webhook] Failed to fetch PR details`)
|
|
60
|
-
return Response.json({ error: "Failed to fetch PR details" }, { status: 500 })
|
|
61
|
-
}
|
|
62
|
-
|
|
63
|
-
// Get changed files
|
|
64
|
-
const changedFiles = await getChangedFiles(owner, repo, prNumber)
|
|
65
|
-
|
|
66
|
-
// Trigger the check-pr workflow
|
|
67
|
-
console.log(`[Webhook] Triggering PR check workflow`)
|
|
68
|
-
|
|
69
|
-
const workflowUrl = "https://dev3000-mcp.vercel.sh/api/cloud/check-pr"
|
|
70
|
-
|
|
71
|
-
const workflowResponse = await fetch(workflowUrl, {
|
|
72
|
-
method: "POST",
|
|
73
|
-
headers: {
|
|
74
|
-
"Content-Type": "application/json"
|
|
75
|
-
},
|
|
76
|
-
body: JSON.stringify({
|
|
77
|
-
previewUrl,
|
|
78
|
-
prTitle: prDetails.title,
|
|
79
|
-
prBody: prDetails.body,
|
|
80
|
-
changedFiles,
|
|
81
|
-
repoOwner: owner,
|
|
82
|
-
repoName: repo,
|
|
83
|
-
prNumber
|
|
84
|
-
})
|
|
85
|
-
})
|
|
86
|
-
|
|
87
|
-
if (!workflowResponse.ok) {
|
|
88
|
-
console.error(`[Webhook] Workflow failed: ${workflowResponse.status}`)
|
|
89
|
-
return Response.json({ error: "Workflow failed" }, { status: 500 })
|
|
90
|
-
}
|
|
91
|
-
|
|
92
|
-
const result = await workflowResponse.json()
|
|
93
|
-
|
|
94
|
-
console.log(`[Webhook] Workflow completed: ${result.success ? "success" : "failed"}`)
|
|
95
|
-
console.log(`[Webhook] Report URL: ${result.reportUrl}`)
|
|
96
|
-
|
|
97
|
-
// Optional: Post comment on PR if GITHUB_TOKEN is set
|
|
98
|
-
if (process.env.GITHUB_TOKEN) {
|
|
99
|
-
await postPRComment(owner, repo, prNumber, result)
|
|
100
|
-
await setGitHubCheck(owner, repo, deployment.meta.githubCommitSha, result)
|
|
101
|
-
console.log(`[Webhook] Posted results to PR`)
|
|
102
|
-
} else {
|
|
103
|
-
console.log(`[Webhook] Skipping GitHub comment (no GITHUB_TOKEN set)`)
|
|
104
|
-
console.log(`[Webhook] View report at: ${result.reportUrl}`)
|
|
105
|
-
}
|
|
106
|
-
|
|
107
|
-
return Response.json({
|
|
108
|
-
success: true,
|
|
109
|
-
prNumber,
|
|
110
|
-
reportUrl: result.reportUrl,
|
|
111
|
-
checksPassed: result.success,
|
|
112
|
-
githubCommentPosted: !!process.env.GITHUB_TOKEN
|
|
113
|
-
})
|
|
114
|
-
} catch (error) {
|
|
115
|
-
console.error("[Webhook] Error:", error)
|
|
116
|
-
return Response.json({ error: error instanceof Error ? error.message : "Unknown error" }, { status: 500 })
|
|
117
|
-
}
|
|
118
|
-
}
|
|
119
|
-
|
|
120
|
-
/**
|
|
121
|
-
* Find PR number for a given branch
|
|
122
|
-
*/
|
|
123
|
-
async function findPRNumber(owner: string, repo: string, branch: string): Promise<string | null> {
|
|
124
|
-
try {
|
|
125
|
-
const headers: Record<string, string> = {
|
|
126
|
-
Accept: "application/vnd.github.v3+json"
|
|
127
|
-
}
|
|
128
|
-
|
|
129
|
-
// Only add Authorization header if token is available
|
|
130
|
-
if (process.env.GITHUB_TOKEN) {
|
|
131
|
-
headers.Authorization = `Bearer ${process.env.GITHUB_TOKEN}`
|
|
132
|
-
}
|
|
133
|
-
|
|
134
|
-
const response = await fetch(
|
|
135
|
-
`https://api.github.com/repos/${owner}/${repo}/pulls?head=${owner}:${branch}&state=open`,
|
|
136
|
-
{ headers }
|
|
137
|
-
)
|
|
138
|
-
|
|
139
|
-
if (!response.ok) {
|
|
140
|
-
console.error(`Failed to find PR: ${response.status}`)
|
|
141
|
-
return null
|
|
142
|
-
}
|
|
143
|
-
|
|
144
|
-
const prs = await response.json()
|
|
145
|
-
return prs.length > 0 ? String(prs[0].number) : null
|
|
146
|
-
} catch (error) {
|
|
147
|
-
console.error("Error finding PR:", error)
|
|
148
|
-
return null
|
|
149
|
-
}
|
|
150
|
-
}
|
|
151
|
-
|
|
152
|
-
/**
|
|
153
|
-
* Fetch PR details from GitHub
|
|
154
|
-
*/
|
|
155
|
-
async function fetchPRDetails(owner: string, repo: string, prNumber: string) {
|
|
156
|
-
try {
|
|
157
|
-
const headers: Record<string, string> = {
|
|
158
|
-
Accept: "application/vnd.github.v3+json"
|
|
159
|
-
}
|
|
160
|
-
|
|
161
|
-
// Only add Authorization header if token is available
|
|
162
|
-
if (process.env.GITHUB_TOKEN) {
|
|
163
|
-
headers.Authorization = `Bearer ${process.env.GITHUB_TOKEN}`
|
|
164
|
-
}
|
|
165
|
-
|
|
166
|
-
const response = await fetch(`https://api.github.com/repos/${owner}/${repo}/pulls/${prNumber}`, {
|
|
167
|
-
headers
|
|
168
|
-
})
|
|
169
|
-
|
|
170
|
-
if (!response.ok) {
|
|
171
|
-
return null
|
|
172
|
-
}
|
|
173
|
-
|
|
174
|
-
const pr = await response.json()
|
|
175
|
-
return {
|
|
176
|
-
title: pr.title,
|
|
177
|
-
body: pr.body || "",
|
|
178
|
-
number: pr.number
|
|
179
|
-
}
|
|
180
|
-
} catch (error) {
|
|
181
|
-
console.error("Error fetching PR details:", error)
|
|
182
|
-
return null
|
|
183
|
-
}
|
|
184
|
-
}
|
|
185
|
-
|
|
186
|
-
/**
|
|
187
|
-
* Get changed files in PR
|
|
188
|
-
*/
|
|
189
|
-
async function getChangedFiles(owner: string, repo: string, prNumber: string): Promise<string[]> {
|
|
190
|
-
try {
|
|
191
|
-
const headers: Record<string, string> = {
|
|
192
|
-
Accept: "application/vnd.github.v3+json"
|
|
193
|
-
}
|
|
194
|
-
|
|
195
|
-
// Only add Authorization header if token is available
|
|
196
|
-
if (process.env.GITHUB_TOKEN) {
|
|
197
|
-
headers.Authorization = `Bearer ${process.env.GITHUB_TOKEN}`
|
|
198
|
-
}
|
|
199
|
-
|
|
200
|
-
const response = await fetch(`https://api.github.com/repos/${owner}/${repo}/pulls/${prNumber}/files`, {
|
|
201
|
-
headers
|
|
202
|
-
})
|
|
203
|
-
|
|
204
|
-
if (!response.ok) {
|
|
205
|
-
return []
|
|
206
|
-
}
|
|
207
|
-
|
|
208
|
-
const files = await response.json()
|
|
209
|
-
// biome-ignore lint/suspicious/noExplicitAny: GitHub API file objects have dynamic structure
|
|
210
|
-
return files.map((file: any) => file.filename)
|
|
211
|
-
} catch (error) {
|
|
212
|
-
console.error("Error getting changed files:", error)
|
|
213
|
-
return []
|
|
214
|
-
}
|
|
215
|
-
}
|
|
216
|
-
|
|
217
|
-
/**
|
|
218
|
-
* Post comment on PR with results
|
|
219
|
-
*/
|
|
220
|
-
// biome-ignore lint/suspicious/noExplicitAny: Workflow result object has dynamic structure
|
|
221
|
-
async function postPRComment(owner: string, repo: string, prNumber: string, result: any) {
|
|
222
|
-
try {
|
|
223
|
-
const statusEmoji = result.success ? "✅" : "❌"
|
|
224
|
-
const comment = `## ${statusEmoji} dev3000 PR Check Results
|
|
225
|
-
|
|
226
|
-
**Status**: ${result.success ? "All checks passed" : "Some checks failed"}
|
|
227
|
-
|
|
228
|
-
### Summary
|
|
229
|
-
${result.verification?.summary || "Check completed"}
|
|
230
|
-
|
|
231
|
-
${result.performance?.slowPagesCount > 0 ? `\n⚠️ **Performance**: ${result.performance.slowPagesCount} slow page(s) detected` : ""}
|
|
232
|
-
|
|
233
|
-
**Full Report**: [View Details](${result.reportUrl})
|
|
234
|
-
|
|
235
|
-
---
|
|
236
|
-
*Powered by [dev3000](https://github.com/vercel-labs/dev3000)*`
|
|
237
|
-
|
|
238
|
-
const response = await fetch(`https://api.github.com/repos/${owner}/${repo}/issues/${prNumber}/comments`, {
|
|
239
|
-
method: "POST",
|
|
240
|
-
headers: {
|
|
241
|
-
Accept: "application/vnd.github.v3+json",
|
|
242
|
-
Authorization: `Bearer ${process.env.GITHUB_TOKEN}`,
|
|
243
|
-
"Content-Type": "application/json"
|
|
244
|
-
},
|
|
245
|
-
body: JSON.stringify({ body: comment })
|
|
246
|
-
})
|
|
247
|
-
|
|
248
|
-
if (!response.ok) {
|
|
249
|
-
console.error(`Failed to post comment: ${response.status}`)
|
|
250
|
-
} else {
|
|
251
|
-
console.log(`[Webhook] Posted comment on PR #${prNumber}`)
|
|
252
|
-
}
|
|
253
|
-
} catch (error) {
|
|
254
|
-
console.error("Error posting PR comment:", error)
|
|
255
|
-
}
|
|
256
|
-
}
|
|
257
|
-
|
|
258
|
-
/**
|
|
259
|
-
* Set GitHub Check status
|
|
260
|
-
*/
|
|
261
|
-
// biome-ignore lint/suspicious/noExplicitAny: Workflow result object has dynamic structure
|
|
262
|
-
async function setGitHubCheck(owner: string, repo: string, sha: string, result: any) {
|
|
263
|
-
try {
|
|
264
|
-
const status = result.success ? "success" : "failure"
|
|
265
|
-
const description = result.success ? "All PR checks passed" : "Some PR checks failed"
|
|
266
|
-
|
|
267
|
-
const response = await fetch(`https://api.github.com/repos/${owner}/${repo}/statuses/${sha}`, {
|
|
268
|
-
method: "POST",
|
|
269
|
-
headers: {
|
|
270
|
-
Accept: "application/vnd.github.v3+json",
|
|
271
|
-
Authorization: `Bearer ${process.env.GITHUB_TOKEN}`,
|
|
272
|
-
"Content-Type": "application/json"
|
|
273
|
-
},
|
|
274
|
-
body: JSON.stringify({
|
|
275
|
-
state: status,
|
|
276
|
-
target_url: result.reportUrl,
|
|
277
|
-
description,
|
|
278
|
-
context: "dev3000/pr-check"
|
|
279
|
-
})
|
|
280
|
-
})
|
|
281
|
-
|
|
282
|
-
if (!response.ok) {
|
|
283
|
-
console.error(`Failed to set GitHub check: ${response.status}`)
|
|
284
|
-
} else {
|
|
285
|
-
console.log(`[Webhook] Set GitHub check status: ${status}`)
|
|
286
|
-
}
|
|
287
|
-
} catch (error) {
|
|
288
|
-
console.error("Error setting GitHub check:", error)
|
|
289
|
-
}
|
|
290
|
-
}
|
|
@@ -1,48 +0,0 @@
|
|
|
1
|
-
import { cookies } from "next/headers"
|
|
2
|
-
|
|
3
|
-
export async function POST(request: Request, { params }: { params: Promise<{ projectId: string }> }) {
|
|
4
|
-
try {
|
|
5
|
-
const { projectId } = await params
|
|
6
|
-
const url = new URL(request.url)
|
|
7
|
-
const teamId = url.searchParams.get("teamId")
|
|
8
|
-
|
|
9
|
-
// Get access token from cookies
|
|
10
|
-
const cookieStore = await cookies()
|
|
11
|
-
const accessToken = cookieStore.get("access_token")?.value
|
|
12
|
-
|
|
13
|
-
if (!accessToken) {
|
|
14
|
-
return Response.json({ error: "Unauthorized" }, { status: 401 })
|
|
15
|
-
}
|
|
16
|
-
|
|
17
|
-
// Generate/update deployment protection bypass token for the project
|
|
18
|
-
const response = await fetch(
|
|
19
|
-
`https://api.vercel.com/v1/projects/${projectId}/protection-bypass${teamId ? `?teamId=${teamId}` : ""}`,
|
|
20
|
-
{
|
|
21
|
-
method: "PATCH",
|
|
22
|
-
headers: {
|
|
23
|
-
Authorization: `Bearer ${accessToken}`,
|
|
24
|
-
"Content-Type": "application/json"
|
|
25
|
-
},
|
|
26
|
-
body: JSON.stringify({
|
|
27
|
-
generate: true
|
|
28
|
-
})
|
|
29
|
-
}
|
|
30
|
-
)
|
|
31
|
-
|
|
32
|
-
if (!response.ok) {
|
|
33
|
-
const error = await response.text()
|
|
34
|
-
console.error("Failed to generate bypass token:", response.status, error)
|
|
35
|
-
return Response.json({ error: "Failed to generate bypass token", details: error }, { status: response.status })
|
|
36
|
-
}
|
|
37
|
-
|
|
38
|
-
const data = await response.json()
|
|
39
|
-
|
|
40
|
-
return Response.json({
|
|
41
|
-
success: true,
|
|
42
|
-
token: data.secret
|
|
43
|
-
})
|
|
44
|
-
} catch (error) {
|
|
45
|
-
console.error("Error generating bypass token:", error)
|
|
46
|
-
return Response.json({ error: error instanceof Error ? error.message : String(error) }, { status: 500 })
|
|
47
|
-
}
|
|
48
|
-
}
|
|
@@ -1,115 +0,0 @@
|
|
|
1
|
-
import { getValidAccessToken } from "@/lib/auth"
|
|
2
|
-
|
|
3
|
-
/**
|
|
4
|
-
* API Route to fetch branches with recent deployments for a project
|
|
5
|
-
*
|
|
6
|
-
* Query params:
|
|
7
|
-
* - projectId: Vercel project ID (required)
|
|
8
|
-
* - teamId: Team ID or username (optional)
|
|
9
|
-
* - limit: Number of deployments to check (default: 20)
|
|
10
|
-
*/
|
|
11
|
-
export async function GET(request: Request) {
|
|
12
|
-
try {
|
|
13
|
-
const accessToken = await getValidAccessToken()
|
|
14
|
-
|
|
15
|
-
if (!accessToken) {
|
|
16
|
-
return Response.json({ error: "Not authenticated" }, { status: 401 })
|
|
17
|
-
}
|
|
18
|
-
|
|
19
|
-
const url = new URL(request.url)
|
|
20
|
-
const projectId = url.searchParams.get("projectId")
|
|
21
|
-
const teamId = url.searchParams.get("teamId")
|
|
22
|
-
const limit = url.searchParams.get("limit") || "20"
|
|
23
|
-
|
|
24
|
-
if (!projectId) {
|
|
25
|
-
return Response.json({ error: "projectId is required" }, { status: 400 })
|
|
26
|
-
}
|
|
27
|
-
|
|
28
|
-
// Build the API URL to fetch recent deployments
|
|
29
|
-
const apiUrl = new URL(`https://api.vercel.com/v6/deployments`)
|
|
30
|
-
apiUrl.searchParams.set("projectId", projectId)
|
|
31
|
-
apiUrl.searchParams.set("limit", limit)
|
|
32
|
-
if (teamId) {
|
|
33
|
-
apiUrl.searchParams.set("teamId", teamId)
|
|
34
|
-
}
|
|
35
|
-
|
|
36
|
-
console.log("Fetching deployments from Vercel API:", apiUrl.toString())
|
|
37
|
-
const response = await fetch(apiUrl.toString(), {
|
|
38
|
-
headers: {
|
|
39
|
-
Authorization: `Bearer ${accessToken}`
|
|
40
|
-
}
|
|
41
|
-
})
|
|
42
|
-
|
|
43
|
-
if (!response.ok) {
|
|
44
|
-
const errorText = await response.text()
|
|
45
|
-
console.error("Failed to fetch deployments:", response.status, errorText)
|
|
46
|
-
return Response.json(
|
|
47
|
-
{ error: `Failed to fetch deployments: ${response.status} ${errorText}` },
|
|
48
|
-
{ status: response.status }
|
|
49
|
-
)
|
|
50
|
-
}
|
|
51
|
-
|
|
52
|
-
const data = await response.json()
|
|
53
|
-
console.log(`Fetched ${data.deployments?.length || 0} deployments`)
|
|
54
|
-
|
|
55
|
-
// Extract unique branches with their latest deployment info
|
|
56
|
-
const branchesMap = new Map<
|
|
57
|
-
string,
|
|
58
|
-
{
|
|
59
|
-
name: string
|
|
60
|
-
lastDeployment: {
|
|
61
|
-
url: string
|
|
62
|
-
createdAt: number
|
|
63
|
-
state: string
|
|
64
|
-
readyState: string
|
|
65
|
-
}
|
|
66
|
-
}
|
|
67
|
-
>()
|
|
68
|
-
|
|
69
|
-
for (const deployment of data.deployments || []) {
|
|
70
|
-
const branch = deployment.meta?.githubCommitRef || deployment.gitSource?.ref || "main"
|
|
71
|
-
|
|
72
|
-
// Only include deployments that are ready
|
|
73
|
-
if (deployment.readyState !== "READY") {
|
|
74
|
-
continue
|
|
75
|
-
}
|
|
76
|
-
|
|
77
|
-
// Only include the latest deployment per branch
|
|
78
|
-
if (!branchesMap.has(branch)) {
|
|
79
|
-
branchesMap.set(branch, {
|
|
80
|
-
name: branch,
|
|
81
|
-
lastDeployment: {
|
|
82
|
-
url: deployment.url,
|
|
83
|
-
createdAt: deployment.createdAt,
|
|
84
|
-
state: deployment.state,
|
|
85
|
-
readyState: deployment.readyState
|
|
86
|
-
}
|
|
87
|
-
})
|
|
88
|
-
}
|
|
89
|
-
}
|
|
90
|
-
|
|
91
|
-
// Convert to array and sort by most recent deployment
|
|
92
|
-
const branches = Array.from(branchesMap.values()).sort(
|
|
93
|
-
(a, b) => b.lastDeployment.createdAt - a.lastDeployment.createdAt
|
|
94
|
-
)
|
|
95
|
-
|
|
96
|
-
console.log(
|
|
97
|
-
`Found ${branches.length} unique branches with ready deployments:`,
|
|
98
|
-
branches.map((b) => b.name)
|
|
99
|
-
)
|
|
100
|
-
|
|
101
|
-
return Response.json({
|
|
102
|
-
success: true,
|
|
103
|
-
branches
|
|
104
|
-
})
|
|
105
|
-
} catch (error) {
|
|
106
|
-
console.error("Error fetching branches:", error)
|
|
107
|
-
return Response.json(
|
|
108
|
-
{
|
|
109
|
-
success: false,
|
|
110
|
-
error: error instanceof Error ? error.message : String(error)
|
|
111
|
-
},
|
|
112
|
-
{ status: 500 }
|
|
113
|
-
)
|
|
114
|
-
}
|
|
115
|
-
}
|
|
@@ -1,33 +0,0 @@
|
|
|
1
|
-
export async function POST(request: Request) {
|
|
2
|
-
try {
|
|
3
|
-
const { url } = await request.json()
|
|
4
|
-
|
|
5
|
-
if (!url) {
|
|
6
|
-
return Response.json({ error: "URL is required" }, { status: 400 })
|
|
7
|
-
}
|
|
8
|
-
|
|
9
|
-
// Make HEAD request to check if deployment is protected
|
|
10
|
-
const response = await fetch(url, {
|
|
11
|
-
method: "HEAD",
|
|
12
|
-
// Don't follow redirects
|
|
13
|
-
redirect: "manual"
|
|
14
|
-
})
|
|
15
|
-
|
|
16
|
-
// If we get 401, deployment is protected
|
|
17
|
-
const isProtected = response.status === 401
|
|
18
|
-
|
|
19
|
-
return Response.json({
|
|
20
|
-
isProtected,
|
|
21
|
-
status: response.status
|
|
22
|
-
})
|
|
23
|
-
} catch (error) {
|
|
24
|
-
console.error("Error checking deployment protection:", error)
|
|
25
|
-
return Response.json(
|
|
26
|
-
{
|
|
27
|
-
error: error instanceof Error ? error.message : String(error),
|
|
28
|
-
isProtected: false
|
|
29
|
-
},
|
|
30
|
-
{ status: 500 }
|
|
31
|
-
)
|
|
32
|
-
}
|
|
33
|
-
}
|
|
@@ -1,97 +0,0 @@
|
|
|
1
|
-
import { cookies } from "next/headers"
|
|
2
|
-
|
|
3
|
-
/**
|
|
4
|
-
* API Route to fetch Vercel projects for a team/account
|
|
5
|
-
*
|
|
6
|
-
* Query params:
|
|
7
|
-
* - teamId: Team ID or username (optional - if omitted, fetches personal projects)
|
|
8
|
-
*/
|
|
9
|
-
export async function GET(request: Request) {
|
|
10
|
-
try {
|
|
11
|
-
const cookieStore = await cookies()
|
|
12
|
-
const accessToken = cookieStore.get("access_token")?.value
|
|
13
|
-
|
|
14
|
-
if (!accessToken) {
|
|
15
|
-
return Response.json({ error: "Not authenticated" }, { status: 401 })
|
|
16
|
-
}
|
|
17
|
-
|
|
18
|
-
const url = new URL(request.url)
|
|
19
|
-
const teamId = url.searchParams.get("teamId")
|
|
20
|
-
|
|
21
|
-
// Build the API URL with optional teamId parameter
|
|
22
|
-
const apiUrl = new URL("https://api.vercel.com/v9/projects")
|
|
23
|
-
if (teamId) {
|
|
24
|
-
apiUrl.searchParams.set("teamId", teamId)
|
|
25
|
-
}
|
|
26
|
-
|
|
27
|
-
// Fetch projects from Vercel API
|
|
28
|
-
console.log("Fetching projects from Vercel API:", apiUrl.toString())
|
|
29
|
-
const response = await fetch(apiUrl.toString(), {
|
|
30
|
-
headers: {
|
|
31
|
-
Authorization: `Bearer ${accessToken}`
|
|
32
|
-
}
|
|
33
|
-
})
|
|
34
|
-
|
|
35
|
-
if (!response.ok) {
|
|
36
|
-
const errorText = await response.text()
|
|
37
|
-
console.error("Failed to fetch projects:", response.status, errorText)
|
|
38
|
-
return Response.json(
|
|
39
|
-
{ error: `Failed to fetch projects: ${response.status} ${errorText}` },
|
|
40
|
-
{ status: response.status }
|
|
41
|
-
)
|
|
42
|
-
}
|
|
43
|
-
|
|
44
|
-
const data = await response.json()
|
|
45
|
-
console.log("Projects API response:", JSON.stringify(data, null, 2))
|
|
46
|
-
console.log(`Fetched ${data.projects?.length || 0} projects from Vercel API`)
|
|
47
|
-
|
|
48
|
-
// Handle case where no projects exist
|
|
49
|
-
if (!data.projects || data.projects.length === 0) {
|
|
50
|
-
return Response.json({
|
|
51
|
-
success: true,
|
|
52
|
-
projects: []
|
|
53
|
-
})
|
|
54
|
-
}
|
|
55
|
-
|
|
56
|
-
// Format projects data
|
|
57
|
-
// biome-ignore lint/suspicious/noExplicitAny: Vercel API response shape is external
|
|
58
|
-
const projects = data.projects.map((project: any) => ({
|
|
59
|
-
id: project.id,
|
|
60
|
-
name: project.name,
|
|
61
|
-
framework: project.framework,
|
|
62
|
-
link: project.link,
|
|
63
|
-
latestDeployments:
|
|
64
|
-
// biome-ignore lint/suspicious/noExplicitAny: Vercel API response shape is external
|
|
65
|
-
project.latestDeployments?.map((deployment: any) => ({
|
|
66
|
-
id: deployment.id,
|
|
67
|
-
url: deployment.url,
|
|
68
|
-
state: deployment.state,
|
|
69
|
-
readyState: deployment.readyState,
|
|
70
|
-
createdAt: deployment.createdAt,
|
|
71
|
-
gitSource: deployment.gitSource
|
|
72
|
-
? {
|
|
73
|
-
type: deployment.gitSource.type,
|
|
74
|
-
repoId: deployment.gitSource.repoId,
|
|
75
|
-
ref: deployment.gitSource.ref,
|
|
76
|
-
sha: deployment.gitSource.sha,
|
|
77
|
-
message: deployment.gitSource.message
|
|
78
|
-
}
|
|
79
|
-
: null
|
|
80
|
-
})) || []
|
|
81
|
-
}))
|
|
82
|
-
|
|
83
|
-
return Response.json({
|
|
84
|
-
success: true,
|
|
85
|
-
projects
|
|
86
|
-
})
|
|
87
|
-
} catch (error) {
|
|
88
|
-
console.error("Error fetching projects:", error)
|
|
89
|
-
return Response.json(
|
|
90
|
-
{
|
|
91
|
-
success: false,
|
|
92
|
-
error: error instanceof Error ? error.message : String(error)
|
|
93
|
-
},
|
|
94
|
-
{ status: 500 }
|
|
95
|
-
)
|
|
96
|
-
}
|
|
97
|
-
}
|
|
@@ -1,105 +0,0 @@
|
|
|
1
|
-
import { deleteWorkflowRuns, listWorkflowRuns } from "@/lib/workflow-storage"
|
|
2
|
-
|
|
3
|
-
// CORS headers - allowing credentials from localhost
|
|
4
|
-
const corsHeaders = {
|
|
5
|
-
"Access-Control-Allow-Origin": "http://localhost:3000",
|
|
6
|
-
"Access-Control-Allow-Methods": "GET, DELETE, OPTIONS",
|
|
7
|
-
"Access-Control-Allow-Headers": "Content-Type, Authorization",
|
|
8
|
-
"Access-Control-Allow-Credentials": "true"
|
|
9
|
-
}
|
|
10
|
-
|
|
11
|
-
// Handle OPTIONS preflight request
|
|
12
|
-
export async function OPTIONS() {
|
|
13
|
-
return new Response(null, {
|
|
14
|
-
status: 204,
|
|
15
|
-
headers: corsHeaders
|
|
16
|
-
})
|
|
17
|
-
}
|
|
18
|
-
|
|
19
|
-
/**
|
|
20
|
-
* GET /api/workflows
|
|
21
|
-
* Fetches all workflow runs for a user
|
|
22
|
-
*
|
|
23
|
-
* Query params:
|
|
24
|
-
* - userId: Required. The user ID to fetch runs for
|
|
25
|
-
*/
|
|
26
|
-
export async function GET(request: Request) {
|
|
27
|
-
try {
|
|
28
|
-
const { searchParams } = new URL(request.url)
|
|
29
|
-
const userId = searchParams.get("userId")
|
|
30
|
-
|
|
31
|
-
if (!userId) {
|
|
32
|
-
return Response.json({ error: "userId is required" }, { status: 400, headers: corsHeaders })
|
|
33
|
-
}
|
|
34
|
-
|
|
35
|
-
console.log(`[Workflows API] Fetching runs for user: ${userId}`)
|
|
36
|
-
|
|
37
|
-
const runs = await listWorkflowRuns(userId)
|
|
38
|
-
|
|
39
|
-
console.log(`[Workflows API] Found ${runs.length} runs`)
|
|
40
|
-
|
|
41
|
-
return Response.json(
|
|
42
|
-
{
|
|
43
|
-
success: true,
|
|
44
|
-
runs
|
|
45
|
-
},
|
|
46
|
-
{ headers: corsHeaders }
|
|
47
|
-
)
|
|
48
|
-
} catch (error) {
|
|
49
|
-
console.error("[Workflows API] Error fetching workflow runs:", error)
|
|
50
|
-
return Response.json(
|
|
51
|
-
{
|
|
52
|
-
success: false,
|
|
53
|
-
error: error instanceof Error ? error.message : String(error)
|
|
54
|
-
},
|
|
55
|
-
{ status: 500, headers: corsHeaders }
|
|
56
|
-
)
|
|
57
|
-
}
|
|
58
|
-
}
|
|
59
|
-
|
|
60
|
-
/**
|
|
61
|
-
* DELETE /api/workflows
|
|
62
|
-
* Deletes workflow runs and their associated blobs
|
|
63
|
-
*
|
|
64
|
-
* Body:
|
|
65
|
-
* - userId: Required. The user ID
|
|
66
|
-
* - runIds: Required. Array of run IDs to delete
|
|
67
|
-
*/
|
|
68
|
-
export async function DELETE(request: Request) {
|
|
69
|
-
try {
|
|
70
|
-
const body = await request.json()
|
|
71
|
-
const { userId, runIds } = body
|
|
72
|
-
|
|
73
|
-
if (!userId) {
|
|
74
|
-
return Response.json({ error: "userId is required" }, { status: 400, headers: corsHeaders })
|
|
75
|
-
}
|
|
76
|
-
|
|
77
|
-
if (!runIds || !Array.isArray(runIds) || runIds.length === 0) {
|
|
78
|
-
return Response.json({ error: "runIds array is required" }, { status: 400, headers: corsHeaders })
|
|
79
|
-
}
|
|
80
|
-
|
|
81
|
-
console.log(`[Workflows API] Deleting ${runIds.length} runs for user: ${userId}`)
|
|
82
|
-
|
|
83
|
-
const result = await deleteWorkflowRuns(userId, runIds)
|
|
84
|
-
|
|
85
|
-
console.log(`[Workflows API] Deleted ${result.deleted} runs, ${result.errors.length} errors`)
|
|
86
|
-
|
|
87
|
-
return Response.json(
|
|
88
|
-
{
|
|
89
|
-
success: true,
|
|
90
|
-
deleted: result.deleted,
|
|
91
|
-
errors: result.errors
|
|
92
|
-
},
|
|
93
|
-
{ headers: corsHeaders }
|
|
94
|
-
)
|
|
95
|
-
} catch (error) {
|
|
96
|
-
console.error("[Workflows API] Error deleting workflow runs:", error)
|
|
97
|
-
return Response.json(
|
|
98
|
-
{
|
|
99
|
-
success: false,
|
|
100
|
-
error: error instanceof Error ? error.message : String(error)
|
|
101
|
-
},
|
|
102
|
-
{ status: 500, headers: corsHeaders }
|
|
103
|
-
)
|
|
104
|
-
}
|
|
105
|
-
}
|