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,458 +0,0 @@
|
|
|
1
|
-
/**
|
|
2
|
-
* Step functions for check-pr workflow
|
|
3
|
-
* Separated into their own module to avoid workflow bundler issues
|
|
4
|
-
*/
|
|
5
|
-
|
|
6
|
-
import { put } from "@vercel/blob"
|
|
7
|
-
import { createGateway, generateText } from "ai"
|
|
8
|
-
|
|
9
|
-
/**
|
|
10
|
-
* Step 1: Identify affected pages based on changed files
|
|
11
|
-
*/
|
|
12
|
-
export async function identifyAffectedPages(changedFiles: string[], prBody: string) {
|
|
13
|
-
"use step"
|
|
14
|
-
|
|
15
|
-
console.log(`[Step 1] Identifying affected pages from ${changedFiles.length} changed files...`)
|
|
16
|
-
|
|
17
|
-
// Common patterns to identify page files
|
|
18
|
-
const pagePatterns = [
|
|
19
|
-
/\/pages\/(.*)\.(tsx?|jsx?)$/, // Next.js pages dir
|
|
20
|
-
/\/app\/(.*)\/(page|route)\.(tsx?|jsx?)$/, // Next.js app dir
|
|
21
|
-
/\/routes\/(.*)\.(tsx?|jsx?)$/, // SvelteKit routes
|
|
22
|
-
/\/src\/routes\/(.*)\.(svelte|tsx?|jsx?)$/, // SvelteKit src/routes
|
|
23
|
-
/\.page\.(tsx?|jsx?)$/, // Generic page pattern
|
|
24
|
-
/\.route\.(tsx?|jsx?)$/ // Generic route pattern
|
|
25
|
-
]
|
|
26
|
-
|
|
27
|
-
const detectedPages: string[] = []
|
|
28
|
-
|
|
29
|
-
for (const file of changedFiles) {
|
|
30
|
-
for (const pattern of pagePatterns) {
|
|
31
|
-
const match = file.match(pattern)
|
|
32
|
-
if (match) {
|
|
33
|
-
let pagePath = match[1] || ""
|
|
34
|
-
// Clean up the path
|
|
35
|
-
pagePath = pagePath.replace(/\/(page|route|index)$/, "")
|
|
36
|
-
pagePath = pagePath || "/"
|
|
37
|
-
if (!pagePath.startsWith("/")) {
|
|
38
|
-
pagePath = `/${pagePath}`
|
|
39
|
-
}
|
|
40
|
-
if (!detectedPages.includes(pagePath)) {
|
|
41
|
-
detectedPages.push(pagePath)
|
|
42
|
-
}
|
|
43
|
-
break
|
|
44
|
-
}
|
|
45
|
-
}
|
|
46
|
-
}
|
|
47
|
-
|
|
48
|
-
console.log(`[Step 1] Detected ${detectedPages.length} pages from file patterns`)
|
|
49
|
-
|
|
50
|
-
// Use AI to analyze PR body for additional pages mentioned
|
|
51
|
-
if (prBody && prBody.length > 10) {
|
|
52
|
-
console.log("[Step 1] Analyzing PR description for mentioned pages...")
|
|
53
|
-
|
|
54
|
-
const gateway = createGateway({
|
|
55
|
-
apiKey: process.env.AI_GATEWAY_API_KEY,
|
|
56
|
-
baseURL: "https://ai-gateway.vercel.sh/v1/ai"
|
|
57
|
-
})
|
|
58
|
-
|
|
59
|
-
const model = gateway("anthropic/claude-sonnet-4-20250514")
|
|
60
|
-
|
|
61
|
-
const aiPrompt = `Analyze this PR description and extract any URL paths or routes that are mentioned or affected.
|
|
62
|
-
|
|
63
|
-
PR Description:
|
|
64
|
-
${prBody}
|
|
65
|
-
|
|
66
|
-
Changed files:
|
|
67
|
-
${changedFiles.join("\n")}
|
|
68
|
-
|
|
69
|
-
Return ONLY a JSON array of paths (e.g., ["/", "/about", "/api/users"]). If no specific paths are mentioned, return an empty array [].
|
|
70
|
-
Do not include explanations, just the JSON array.`
|
|
71
|
-
|
|
72
|
-
try {
|
|
73
|
-
const { text } = await generateText({
|
|
74
|
-
model,
|
|
75
|
-
prompt: aiPrompt,
|
|
76
|
-
// @ts-expect-error - AI SDK types for maxTokens are incomplete
|
|
77
|
-
maxTokens: 500
|
|
78
|
-
})
|
|
79
|
-
|
|
80
|
-
// Parse AI response
|
|
81
|
-
const jsonMatch = text.match(/\[.*\]/)
|
|
82
|
-
if (jsonMatch) {
|
|
83
|
-
const aiPages = JSON.parse(jsonMatch[0]) as string[]
|
|
84
|
-
for (const page of aiPages) {
|
|
85
|
-
if (page && !detectedPages.includes(page)) {
|
|
86
|
-
detectedPages.push(page)
|
|
87
|
-
}
|
|
88
|
-
}
|
|
89
|
-
console.log(`[Step 1] AI found ${aiPages.length} additional pages`)
|
|
90
|
-
}
|
|
91
|
-
} catch (error) {
|
|
92
|
-
console.error("[Step 1] AI analysis failed:", error)
|
|
93
|
-
}
|
|
94
|
-
}
|
|
95
|
-
|
|
96
|
-
// Always include homepage if nothing else is found
|
|
97
|
-
if (detectedPages.length === 0) {
|
|
98
|
-
console.log("[Step 1] No specific pages detected, checking homepage")
|
|
99
|
-
detectedPages.push("/")
|
|
100
|
-
}
|
|
101
|
-
|
|
102
|
-
console.log(`[Step 1] Final pages to check: ${detectedPages.join(", ")}`)
|
|
103
|
-
return detectedPages
|
|
104
|
-
}
|
|
105
|
-
|
|
106
|
-
/**
|
|
107
|
-
* Step 2: Crawl preview deployment pages
|
|
108
|
-
*/
|
|
109
|
-
export async function crawlPreviewPages(previewUrl: string, pagesToCheck: string[]) {
|
|
110
|
-
"use step"
|
|
111
|
-
|
|
112
|
-
console.log(`[Step 2] Crawling ${pagesToCheck.length} pages on ${previewUrl}`)
|
|
113
|
-
|
|
114
|
-
const results = []
|
|
115
|
-
|
|
116
|
-
for (const page of pagesToCheck) {
|
|
117
|
-
console.log(`[Step 2] Crawling: ${page}`)
|
|
118
|
-
|
|
119
|
-
try {
|
|
120
|
-
const fullUrl = `${previewUrl}${page}`
|
|
121
|
-
const response = await fetch(fullUrl, {
|
|
122
|
-
method: "GET",
|
|
123
|
-
headers: {
|
|
124
|
-
"User-Agent": "dev3000-pr-checker/1.0",
|
|
125
|
-
Accept: "text/html,application/json,*/*"
|
|
126
|
-
}
|
|
127
|
-
})
|
|
128
|
-
|
|
129
|
-
const contentType = response.headers.get("content-type") || ""
|
|
130
|
-
const isHtml = contentType.includes("text/html")
|
|
131
|
-
const isJson = contentType.includes("application/json")
|
|
132
|
-
|
|
133
|
-
let content = ""
|
|
134
|
-
let bodyPreview = ""
|
|
135
|
-
|
|
136
|
-
if (isHtml || isJson) {
|
|
137
|
-
content = await response.text()
|
|
138
|
-
// Get first 1000 chars as preview
|
|
139
|
-
bodyPreview = content.substring(0, 1000)
|
|
140
|
-
}
|
|
141
|
-
|
|
142
|
-
results.push({
|
|
143
|
-
page,
|
|
144
|
-
url: fullUrl,
|
|
145
|
-
status: response.status,
|
|
146
|
-
statusText: response.statusText,
|
|
147
|
-
contentType,
|
|
148
|
-
bodyPreview,
|
|
149
|
-
headers: Object.fromEntries(response.headers.entries())
|
|
150
|
-
})
|
|
151
|
-
|
|
152
|
-
console.log(`[Step 2] ${page}: ${response.status} ${response.statusText}`)
|
|
153
|
-
} catch (error) {
|
|
154
|
-
console.error(`[Step 2] Failed to crawl ${page}:`, error)
|
|
155
|
-
results.push({
|
|
156
|
-
page,
|
|
157
|
-
url: `${previewUrl}${page}`,
|
|
158
|
-
status: 0,
|
|
159
|
-
statusText: "Fetch failed",
|
|
160
|
-
error: error instanceof Error ? error.message : String(error)
|
|
161
|
-
})
|
|
162
|
-
}
|
|
163
|
-
}
|
|
164
|
-
|
|
165
|
-
console.log(`[Step 2] Crawled ${results.length} pages`)
|
|
166
|
-
return results
|
|
167
|
-
}
|
|
168
|
-
|
|
169
|
-
/**
|
|
170
|
-
* Step 3: Verify PR claims against actual behavior
|
|
171
|
-
*/
|
|
172
|
-
// biome-ignore lint/suspicious/noExplicitAny: AI-generated crawl data has dynamic structure
|
|
173
|
-
export async function verifyPRClaims(prTitle: string, prBody: string, crawlResults: any[], changedFiles: string[]) {
|
|
174
|
-
"use step"
|
|
175
|
-
|
|
176
|
-
console.log("[Step 3] Verifying PR claims against actual behavior...")
|
|
177
|
-
|
|
178
|
-
const gateway = createGateway({
|
|
179
|
-
apiKey: process.env.AI_GATEWAY_API_KEY,
|
|
180
|
-
baseURL: "https://ai-gateway.vercel.sh/v1/ai"
|
|
181
|
-
})
|
|
182
|
-
|
|
183
|
-
const model = gateway("anthropic/claude-sonnet-4-20250514")
|
|
184
|
-
|
|
185
|
-
const aiPrompt = `You are verifying a Pull Request for accuracy. Compare the PR's claims with the actual deployment behavior.
|
|
186
|
-
|
|
187
|
-
PR Title: ${prTitle}
|
|
188
|
-
|
|
189
|
-
PR Description:
|
|
190
|
-
${prBody}
|
|
191
|
-
|
|
192
|
-
Changed Files:
|
|
193
|
-
${changedFiles.join("\n")}
|
|
194
|
-
|
|
195
|
-
Crawl Results from Preview Deployment:
|
|
196
|
-
${JSON.stringify(crawlResults, null, 2)}
|
|
197
|
-
|
|
198
|
-
Your task:
|
|
199
|
-
1. Analyze whether the PR's claimed changes match the actual behavior shown in the crawl results
|
|
200
|
-
2. Check if pages load successfully (200 status codes)
|
|
201
|
-
3. Identify any errors or unexpected behavior
|
|
202
|
-
4. Verify the changes work as described
|
|
203
|
-
|
|
204
|
-
Respond in this exact JSON format:
|
|
205
|
-
{
|
|
206
|
-
"allChecksPassed": boolean,
|
|
207
|
-
"summary": "Brief summary of findings",
|
|
208
|
-
"details": {
|
|
209
|
-
"claimsVerified": ["List of claims that were verified"],
|
|
210
|
-
"issues": ["List of any issues found"],
|
|
211
|
-
"warnings": ["List of warnings"]
|
|
212
|
-
}
|
|
213
|
-
}
|
|
214
|
-
|
|
215
|
-
Only return valid JSON, no additional text.`
|
|
216
|
-
|
|
217
|
-
try {
|
|
218
|
-
const { text } = await generateText({
|
|
219
|
-
model,
|
|
220
|
-
prompt: aiPrompt,
|
|
221
|
-
// @ts-expect-error - AI SDK types for maxTokens are incomplete
|
|
222
|
-
maxTokens: 1500
|
|
223
|
-
})
|
|
224
|
-
|
|
225
|
-
// Parse AI response
|
|
226
|
-
const jsonMatch = text.match(/\{[\s\S]*\}/)
|
|
227
|
-
if (jsonMatch) {
|
|
228
|
-
const verification = JSON.parse(jsonMatch[0])
|
|
229
|
-
console.log(`[Step 3] Verification complete: ${verification.allChecksPassed ? "PASSED" : "FAILED"}`)
|
|
230
|
-
return verification
|
|
231
|
-
}
|
|
232
|
-
|
|
233
|
-
console.error("[Step 3] Failed to parse AI response")
|
|
234
|
-
return {
|
|
235
|
-
allChecksPassed: false,
|
|
236
|
-
summary: "Verification failed - could not parse AI response",
|
|
237
|
-
details: {
|
|
238
|
-
claimsVerified: [],
|
|
239
|
-
issues: ["AI verification returned invalid format"],
|
|
240
|
-
warnings: []
|
|
241
|
-
}
|
|
242
|
-
}
|
|
243
|
-
} catch (error) {
|
|
244
|
-
console.error("[Step 3] Verification failed:", error)
|
|
245
|
-
return {
|
|
246
|
-
allChecksPassed: false,
|
|
247
|
-
summary: `Verification error: ${error instanceof Error ? error.message : String(error)}`,
|
|
248
|
-
details: {
|
|
249
|
-
claimsVerified: [],
|
|
250
|
-
issues: [error instanceof Error ? error.message : String(error)],
|
|
251
|
-
warnings: []
|
|
252
|
-
}
|
|
253
|
-
}
|
|
254
|
-
}
|
|
255
|
-
}
|
|
256
|
-
|
|
257
|
-
/**
|
|
258
|
-
* Step 4: Check performance metrics
|
|
259
|
-
*/
|
|
260
|
-
export async function checkPerformance(previewUrl: string, pagesToCheck: string[]) {
|
|
261
|
-
"use step"
|
|
262
|
-
|
|
263
|
-
console.log(`[Step 4] Checking performance for ${pagesToCheck.length} pages`)
|
|
264
|
-
|
|
265
|
-
const performanceResults = []
|
|
266
|
-
|
|
267
|
-
for (const page of pagesToCheck) {
|
|
268
|
-
console.log(`[Step 4] Measuring performance: ${page}`)
|
|
269
|
-
|
|
270
|
-
try {
|
|
271
|
-
const fullUrl = `${previewUrl}${page}`
|
|
272
|
-
const startTime = Date.now()
|
|
273
|
-
|
|
274
|
-
const response = await fetch(fullUrl, {
|
|
275
|
-
method: "GET",
|
|
276
|
-
headers: {
|
|
277
|
-
"User-Agent": "dev3000-pr-checker/1.0",
|
|
278
|
-
Accept: "text/html,*/*"
|
|
279
|
-
}
|
|
280
|
-
})
|
|
281
|
-
|
|
282
|
-
const endTime = Date.now()
|
|
283
|
-
const loadTime = endTime - startTime
|
|
284
|
-
|
|
285
|
-
const contentLength = Number.parseInt(response.headers.get("content-length") || "0", 10)
|
|
286
|
-
|
|
287
|
-
performanceResults.push({
|
|
288
|
-
page,
|
|
289
|
-
loadTime,
|
|
290
|
-
contentLength,
|
|
291
|
-
status: response.status,
|
|
292
|
-
isSlow: loadTime > 2000 // Consider >2s as slow
|
|
293
|
-
})
|
|
294
|
-
|
|
295
|
-
console.log(`[Step 4] ${page}: ${loadTime}ms, ${contentLength} bytes`)
|
|
296
|
-
} catch (error) {
|
|
297
|
-
console.error(`[Step 4] Performance check failed for ${page}:`, error)
|
|
298
|
-
performanceResults.push({
|
|
299
|
-
page,
|
|
300
|
-
loadTime: 0,
|
|
301
|
-
contentLength: 0,
|
|
302
|
-
status: 0,
|
|
303
|
-
error: error instanceof Error ? error.message : String(error),
|
|
304
|
-
isSlow: false
|
|
305
|
-
})
|
|
306
|
-
}
|
|
307
|
-
}
|
|
308
|
-
|
|
309
|
-
const slowPages = performanceResults.filter((r) => r.isSlow)
|
|
310
|
-
|
|
311
|
-
console.log(`[Step 4] Performance check complete. ${slowPages.length} slow pages found.`)
|
|
312
|
-
|
|
313
|
-
return {
|
|
314
|
-
results: performanceResults,
|
|
315
|
-
slowPagesCount: slowPages.length,
|
|
316
|
-
summary: {
|
|
317
|
-
avgLoadTime: performanceResults.reduce((sum, r) => sum + r.loadTime, 0) / performanceResults.length,
|
|
318
|
-
slowPages: slowPages.map((r) => r.page)
|
|
319
|
-
}
|
|
320
|
-
}
|
|
321
|
-
}
|
|
322
|
-
|
|
323
|
-
/**
|
|
324
|
-
* Step 5: Generate comprehensive report
|
|
325
|
-
*/
|
|
326
|
-
// biome-ignore lint/suspicious/noExplicitAny: Report data has dynamic structure from previous steps
|
|
327
|
-
export async function generateReport(data: any) {
|
|
328
|
-
"use step"
|
|
329
|
-
|
|
330
|
-
console.log("[Step 5] Generating comprehensive report...")
|
|
331
|
-
|
|
332
|
-
const {
|
|
333
|
-
prTitle,
|
|
334
|
-
prBody,
|
|
335
|
-
prNumber,
|
|
336
|
-
previewUrl,
|
|
337
|
-
changedFiles,
|
|
338
|
-
pagesToCheck,
|
|
339
|
-
crawlResults,
|
|
340
|
-
verification,
|
|
341
|
-
performanceResults,
|
|
342
|
-
repoOwner,
|
|
343
|
-
repoName
|
|
344
|
-
} = data
|
|
345
|
-
|
|
346
|
-
let report = `# PR Verification Report\n\n`
|
|
347
|
-
report += `**PR**: #${prNumber} - ${prTitle}\n`
|
|
348
|
-
report += `**Repository**: ${repoOwner}/${repoName}\n`
|
|
349
|
-
report += `**Preview URL**: ${previewUrl}\n`
|
|
350
|
-
report += `**Timestamp**: ${new Date().toISOString()}\n\n`
|
|
351
|
-
|
|
352
|
-
report += `## Summary\n\n`
|
|
353
|
-
report += `${verification.allChecksPassed ? "✅" : "❌"} ${verification.summary}\n\n`
|
|
354
|
-
|
|
355
|
-
report += `## Changed Files\n\n`
|
|
356
|
-
for (const file of changedFiles) {
|
|
357
|
-
report += `- ${file}\n`
|
|
358
|
-
}
|
|
359
|
-
report += `\n`
|
|
360
|
-
|
|
361
|
-
report += `## Pages Checked\n\n`
|
|
362
|
-
for (const page of pagesToCheck) {
|
|
363
|
-
report += `- ${page}\n`
|
|
364
|
-
}
|
|
365
|
-
report += `\n`
|
|
366
|
-
|
|
367
|
-
report += `## Verification Results\n\n`
|
|
368
|
-
if (verification.details.claimsVerified.length > 0) {
|
|
369
|
-
report += `### Claims Verified ✅\n\n`
|
|
370
|
-
for (const claim of verification.details.claimsVerified) {
|
|
371
|
-
report += `- ${claim}\n`
|
|
372
|
-
}
|
|
373
|
-
report += `\n`
|
|
374
|
-
}
|
|
375
|
-
|
|
376
|
-
if (verification.details.issues.length > 0) {
|
|
377
|
-
report += `### Issues Found ❌\n\n`
|
|
378
|
-
for (const issue of verification.details.issues) {
|
|
379
|
-
report += `- ${issue}\n`
|
|
380
|
-
}
|
|
381
|
-
report += `\n`
|
|
382
|
-
}
|
|
383
|
-
|
|
384
|
-
if (verification.details.warnings.length > 0) {
|
|
385
|
-
report += `### Warnings ⚠️\n\n`
|
|
386
|
-
for (const warning of verification.details.warnings) {
|
|
387
|
-
report += `- ${warning}\n`
|
|
388
|
-
}
|
|
389
|
-
report += `\n`
|
|
390
|
-
}
|
|
391
|
-
|
|
392
|
-
report += `## Crawl Results\n\n`
|
|
393
|
-
for (const result of crawlResults) {
|
|
394
|
-
const statusEmoji = result.status === 200 ? "✅" : result.status >= 400 ? "❌" : "⚠️"
|
|
395
|
-
report += `### ${statusEmoji} ${result.page}\n\n`
|
|
396
|
-
report += `- **URL**: ${result.url}\n`
|
|
397
|
-
report += `- **Status**: ${result.status} ${result.statusText}\n`
|
|
398
|
-
if (result.contentType) {
|
|
399
|
-
report += `- **Content-Type**: ${result.contentType}\n`
|
|
400
|
-
}
|
|
401
|
-
if (result.error) {
|
|
402
|
-
report += `- **Error**: ${result.error}\n`
|
|
403
|
-
}
|
|
404
|
-
report += `\n`
|
|
405
|
-
}
|
|
406
|
-
|
|
407
|
-
report += `## Performance Analysis\n\n`
|
|
408
|
-
report += `**Average Load Time**: ${Math.round(performanceResults.summary.avgLoadTime)}ms\n\n`
|
|
409
|
-
|
|
410
|
-
if (performanceResults.slowPagesCount > 0) {
|
|
411
|
-
report += `⚠️ **Slow Pages** (>2s):\n`
|
|
412
|
-
for (const page of performanceResults.summary.slowPages) {
|
|
413
|
-
report += `- ${page}\n`
|
|
414
|
-
}
|
|
415
|
-
report += `\n`
|
|
416
|
-
} else {
|
|
417
|
-
report += `✅ All pages loaded in under 2 seconds\n\n`
|
|
418
|
-
}
|
|
419
|
-
|
|
420
|
-
report += `### Detailed Metrics\n\n`
|
|
421
|
-
for (const result of performanceResults.results) {
|
|
422
|
-
report += `- **${result.page}**: ${result.loadTime}ms (${result.contentLength} bytes)\n`
|
|
423
|
-
}
|
|
424
|
-
report += `\n`
|
|
425
|
-
|
|
426
|
-
report += `## PR Description\n\n`
|
|
427
|
-
report += `${prBody || "(No description provided)"}\n\n`
|
|
428
|
-
|
|
429
|
-
report += `---\n\n`
|
|
430
|
-
report += `*Generated by dev3000 PR Checker*\n`
|
|
431
|
-
|
|
432
|
-
console.log(`[Step 5] Report generated (${report.length} characters)`)
|
|
433
|
-
return report
|
|
434
|
-
}
|
|
435
|
-
|
|
436
|
-
/**
|
|
437
|
-
* Step 6: Upload report to blob storage
|
|
438
|
-
*/
|
|
439
|
-
export async function uploadReport(report: string, repoOwner: string, repoName: string, prNumber: string) {
|
|
440
|
-
"use step"
|
|
441
|
-
|
|
442
|
-
console.log("[Step 6] Uploading report to blob storage...")
|
|
443
|
-
|
|
444
|
-
const timestamp = new Date().toISOString().replace(/[:.]/g, "-")
|
|
445
|
-
const filename = `pr-check-${repoOwner}-${repoName}-pr${prNumber}-${timestamp}.md`
|
|
446
|
-
|
|
447
|
-
const blob = await put(filename, report, {
|
|
448
|
-
access: "public",
|
|
449
|
-
contentType: "text/markdown"
|
|
450
|
-
})
|
|
451
|
-
|
|
452
|
-
console.log(`[Step 6] Report uploaded: ${blob.url}`)
|
|
453
|
-
|
|
454
|
-
return {
|
|
455
|
-
blobUrl: blob.url,
|
|
456
|
-
filename
|
|
457
|
-
}
|
|
458
|
-
}
|
|
@@ -1,109 +0,0 @@
|
|
|
1
|
-
/**
|
|
2
|
-
* Cloud Check PR Workflow Function - Core workflow logic
|
|
3
|
-
*
|
|
4
|
-
* This file contains ONLY the workflow function and step wrappers.
|
|
5
|
-
* It does NOT import workflow/api to avoid bundler issues.
|
|
6
|
-
*/
|
|
7
|
-
|
|
8
|
-
/**
|
|
9
|
-
* Main workflow function that checks a PR's changes
|
|
10
|
-
*/
|
|
11
|
-
export async function cloudCheckPRWorkflow(params: {
|
|
12
|
-
previewUrl: string
|
|
13
|
-
prTitle: string
|
|
14
|
-
prBody: string
|
|
15
|
-
changedFiles: string[]
|
|
16
|
-
repoOwner: string
|
|
17
|
-
repoName: string
|
|
18
|
-
prNumber: string
|
|
19
|
-
}) {
|
|
20
|
-
"use workflow"
|
|
21
|
-
|
|
22
|
-
const { previewUrl, prTitle, prBody, changedFiles, repoOwner, repoName, prNumber } = params
|
|
23
|
-
|
|
24
|
-
console.log("[Workflow] Starting cloud check-pr workflow...")
|
|
25
|
-
console.log(`[Workflow] Preview URL: ${previewUrl}`)
|
|
26
|
-
console.log(`[Workflow] PR #${prNumber}: ${prTitle}`)
|
|
27
|
-
console.log(`[Workflow] Changed files: ${changedFiles.length}`)
|
|
28
|
-
console.log(`[Workflow] Timestamp: ${new Date().toISOString()}`)
|
|
29
|
-
|
|
30
|
-
// Step 1: Determine which pages to check based on changed files
|
|
31
|
-
const pagesToCheck = await identifyAffectedPagesStep(changedFiles, prBody)
|
|
32
|
-
|
|
33
|
-
// Step 2: Crawl the preview deployment
|
|
34
|
-
const crawlResults = await crawlPreviewPagesStep(previewUrl, pagesToCheck)
|
|
35
|
-
|
|
36
|
-
// Step 3: Verify PR claims against actual behavior
|
|
37
|
-
const verification = await verifyPRClaimsStep(prTitle, prBody, crawlResults, changedFiles)
|
|
38
|
-
|
|
39
|
-
// Step 4: Check performance metrics
|
|
40
|
-
const performanceResults = await checkPerformanceStep(previewUrl, pagesToCheck)
|
|
41
|
-
|
|
42
|
-
// Step 5: Generate comprehensive report
|
|
43
|
-
const report = await generateReportStep({
|
|
44
|
-
prTitle,
|
|
45
|
-
prBody,
|
|
46
|
-
prNumber,
|
|
47
|
-
previewUrl,
|
|
48
|
-
changedFiles,
|
|
49
|
-
pagesToCheck,
|
|
50
|
-
crawlResults,
|
|
51
|
-
verification,
|
|
52
|
-
performanceResults,
|
|
53
|
-
repoOwner,
|
|
54
|
-
repoName
|
|
55
|
-
})
|
|
56
|
-
|
|
57
|
-
// Step 6: Upload report to blob storage
|
|
58
|
-
const blobResult = await uploadReportStep(report, repoOwner, repoName, prNumber)
|
|
59
|
-
|
|
60
|
-
return Response.json({
|
|
61
|
-
success: verification.allChecksPassed,
|
|
62
|
-
reportUrl: blobResult.blobUrl,
|
|
63
|
-
prComment: true,
|
|
64
|
-
verification: verification.summary,
|
|
65
|
-
performance: performanceResults.summary,
|
|
66
|
-
message: verification.allChecksPassed
|
|
67
|
-
? "All PR checks passed! ✅"
|
|
68
|
-
: "Some PR checks failed - see report for details"
|
|
69
|
-
})
|
|
70
|
-
}
|
|
71
|
-
|
|
72
|
-
// Step function wrappers that dynamically import the actual implementations
|
|
73
|
-
async function identifyAffectedPagesStep(changedFiles: string[], prBody: string) {
|
|
74
|
-
"use step"
|
|
75
|
-
const { identifyAffectedPages } = await import("./steps")
|
|
76
|
-
return identifyAffectedPages(changedFiles, prBody)
|
|
77
|
-
}
|
|
78
|
-
|
|
79
|
-
async function crawlPreviewPagesStep(previewUrl: string, pagesToCheck: string[]) {
|
|
80
|
-
"use step"
|
|
81
|
-
const { crawlPreviewPages } = await import("./steps")
|
|
82
|
-
return crawlPreviewPages(previewUrl, pagesToCheck)
|
|
83
|
-
}
|
|
84
|
-
|
|
85
|
-
// biome-ignore lint/suspicious/noExplicitAny: AI-generated crawl data has dynamic structure
|
|
86
|
-
async function verifyPRClaimsStep(prTitle: string, prBody: string, crawlResults: any[], changedFiles: string[]) {
|
|
87
|
-
"use step"
|
|
88
|
-
const { verifyPRClaims } = await import("./steps")
|
|
89
|
-
return verifyPRClaims(prTitle, prBody, crawlResults, changedFiles)
|
|
90
|
-
}
|
|
91
|
-
|
|
92
|
-
async function checkPerformanceStep(previewUrl: string, pagesToCheck: string[]) {
|
|
93
|
-
"use step"
|
|
94
|
-
const { checkPerformance } = await import("./steps")
|
|
95
|
-
return checkPerformance(previewUrl, pagesToCheck)
|
|
96
|
-
}
|
|
97
|
-
|
|
98
|
-
// biome-ignore lint/suspicious/noExplicitAny: Report data has dynamic structure from previous steps
|
|
99
|
-
async function generateReportStep(data: any) {
|
|
100
|
-
"use step"
|
|
101
|
-
const { generateReport } = await import("./steps")
|
|
102
|
-
return generateReport(data)
|
|
103
|
-
}
|
|
104
|
-
|
|
105
|
-
async function uploadReportStep(report: string, repoOwner: string, repoName: string, prNumber: string) {
|
|
106
|
-
"use step"
|
|
107
|
-
const { uploadReport } = await import("./steps")
|
|
108
|
-
return uploadReport(report, repoOwner, repoName, prNumber)
|
|
109
|
-
}
|
|
@@ -1,50 +0,0 @@
|
|
|
1
|
-
import { start } from "workflow/api"
|
|
2
|
-
import { cloudFixWorkflow } from "./workflow"
|
|
3
|
-
|
|
4
|
-
/**
|
|
5
|
-
* POST /api/cloud/fix-workflow
|
|
6
|
-
* HTTP endpoint that starts the workflow using the Workflow SDK
|
|
7
|
-
*/
|
|
8
|
-
export async function POST(request: Request) {
|
|
9
|
-
try {
|
|
10
|
-
const body = await request.json()
|
|
11
|
-
const { devUrl, projectName, repoOwner, repoName, baseBranch } = body
|
|
12
|
-
|
|
13
|
-
// Validate required fields
|
|
14
|
-
if (!devUrl || !projectName) {
|
|
15
|
-
return Response.json({ error: "Missing required fields: devUrl, projectName" }, { status: 400 })
|
|
16
|
-
}
|
|
17
|
-
|
|
18
|
-
console.log(`[API] Starting fix workflow for ${projectName}`)
|
|
19
|
-
|
|
20
|
-
// Start the workflow using the Workflow SDK
|
|
21
|
-
// @ts-expect-error - Workflow SDK types are incomplete
|
|
22
|
-
const workflowRun = await start(cloudFixWorkflow, {
|
|
23
|
-
devUrl,
|
|
24
|
-
projectName,
|
|
25
|
-
repoOwner,
|
|
26
|
-
repoName,
|
|
27
|
-
baseBranch: baseBranch || "main"
|
|
28
|
-
})
|
|
29
|
-
|
|
30
|
-
// @ts-expect-error - Workflow SDK types are incomplete
|
|
31
|
-
console.log(`[API] Workflow started: ${workflowRun.id}`)
|
|
32
|
-
|
|
33
|
-
// Wait for workflow to complete (workflows are durable and will continue even if this times out)
|
|
34
|
-
// @ts-expect-error - Workflow SDK types are incomplete
|
|
35
|
-
const result = await workflowRun.result()
|
|
36
|
-
|
|
37
|
-
console.log(`[API] Workflow completed: ${result.status}`)
|
|
38
|
-
|
|
39
|
-
return result
|
|
40
|
-
} catch (error) {
|
|
41
|
-
console.error("[API] Error starting workflow:", error)
|
|
42
|
-
return Response.json(
|
|
43
|
-
{
|
|
44
|
-
error: error instanceof Error ? error.message : "Unknown error",
|
|
45
|
-
details: error instanceof Error ? error.stack : undefined
|
|
46
|
-
},
|
|
47
|
-
{ status: 500 }
|
|
48
|
-
)
|
|
49
|
-
}
|
|
50
|
-
}
|