deploy-mcp 0.1.0 → 0.2.0
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/README.md +90 -5
- package/dist/worker.js +388 -19
- package/package.json +5 -4
package/README.md
CHANGED
|
@@ -195,6 +195,75 @@ All AI tools (except Continue.dev) use the same configuration:
|
|
|
195
195
|
|
|
196
196
|
💡 Replace `your-vercel-token` with your actual Vercel API token from [vercel.com/account/tokens](https://vercel.com/account/tokens)
|
|
197
197
|
|
|
198
|
+
## Deployment Status Badges
|
|
199
|
+
|
|
200
|
+
Get live deployment status badges for your repositories that update in real-time via webhooks.
|
|
201
|
+
|
|
202
|
+
### Badge URLs
|
|
203
|
+
|
|
204
|
+
Add these badges to your README to show live deployment status:
|
|
205
|
+
|
|
206
|
+
```markdown
|
|
207
|
+

|
|
208
|
+
```
|
|
209
|
+
|
|
210
|
+
**Examples:**
|
|
211
|
+
- `https://deploy-mcp.io/badge/john/my-app/vercel`
|
|
212
|
+
- `https://deploy-mcp.io/badge/youruser/yourrepo/vercel`
|
|
213
|
+
|
|
214
|
+
**⚠️ Requirements:**
|
|
215
|
+
- **Public repositories only** - Private repos not supported for security reasons
|
|
216
|
+
- **Vercel Pro/Enterprise plan** - Webhooks required for real-time badge updates
|
|
217
|
+
|
|
218
|
+
**For free tier users:** The MCP server works perfectly for checking deployment status in your AI conversations. Badges are a premium feature requiring paid Vercel plans.
|
|
219
|
+
|
|
220
|
+
### Setting Up Webhooks
|
|
221
|
+
|
|
222
|
+
For badges to show real-time status, configure webhooks in your deployment platform:
|
|
223
|
+
|
|
224
|
+
#### Vercel Setup
|
|
225
|
+
|
|
226
|
+
**Note:** Vercel webhooks require a Pro or Enterprise plan.
|
|
227
|
+
|
|
228
|
+
1. **Go to your Vercel team settings**
|
|
229
|
+
2. **Navigate to "Webhooks" section**
|
|
230
|
+
3. **Click "Create Webhook"**
|
|
231
|
+
4. **Configure the webhook:**
|
|
232
|
+
- **URL**: `https://deploy-mcp.io/webhook/yourusername/yourrepo/vercel`
|
|
233
|
+
- **Events**: Select "Deployment Created", "Deployment Ready", and "Deployment Error"
|
|
234
|
+
- **Projects**: Choose your specific project or leave empty for all projects
|
|
235
|
+
- **Secret**: Leave empty (not required for public repositories)
|
|
236
|
+
5. **Save the webhook**
|
|
237
|
+
|
|
238
|
+
**Important:** Replace `yourusername` and `yourrepo` with your actual GitHub username and repository name.
|
|
239
|
+
|
|
240
|
+
**Note:** Vercel's free tier doesn't support webhooks, so badges won't work. Use the MCP server instead for deployment status in your AI conversations.
|
|
241
|
+
|
|
242
|
+
#### Supported Events
|
|
243
|
+
|
|
244
|
+
The webhook will trigger on:
|
|
245
|
+
- ✅ Deployment started (badge shows "building")
|
|
246
|
+
- ✅ Deployment succeeded (badge shows "success")
|
|
247
|
+
- ❌ Deployment failed (badge shows "failed")
|
|
248
|
+
- ⚠️ Deployment errored (badge shows "error")
|
|
249
|
+
|
|
250
|
+
#### Badge Status Colors
|
|
251
|
+
|
|
252
|
+
- 🟢 **Success** - Green badge when deployment is live
|
|
253
|
+
- 🔴 **Failed** - Red badge when deployment failed
|
|
254
|
+
- 🟡 **Building** - Yellow badge during deployment
|
|
255
|
+
- ⚫ **Unknown** - Grey badge when no status available
|
|
256
|
+
- 🔴 **Error** - Red badge when deployment errored
|
|
257
|
+
|
|
258
|
+
### Testing Your Setup
|
|
259
|
+
|
|
260
|
+
1. **Add the badge** to your repository README
|
|
261
|
+
2. **Configure the webhook** as described above
|
|
262
|
+
3. **Make a commit** and push to trigger a deployment
|
|
263
|
+
4. **Watch the badge** update in real-time during deployment
|
|
264
|
+
|
|
265
|
+
**Initial badge status will show "unknown" until the first webhook is received.**
|
|
266
|
+
|
|
198
267
|
|
|
199
268
|
### Getting API Tokens
|
|
200
269
|
|
|
@@ -232,14 +301,13 @@ Render integration is on our roadmap. [Star the repo](https://github.com/alexpot
|
|
|
232
301
|
|
|
233
302
|
## Usage Examples
|
|
234
303
|
|
|
235
|
-
###
|
|
304
|
+
### MCP Server Usage
|
|
236
305
|
|
|
237
|
-
Once configured
|
|
306
|
+
Once configured with your AI assistant, simply ask:
|
|
238
307
|
|
|
239
308
|
```
|
|
240
309
|
You: Check my latest Vercel deployment
|
|
241
310
|
|
|
242
|
-
|
|
243
311
|
Assistant: I'll check your latest Vercel deployment for you.
|
|
244
312
|
|
|
245
313
|
✅ **Status**: Success
|
|
@@ -256,13 +324,30 @@ Assistant: I'll check your latest Vercel deployment for you.
|
|
|
256
324
|
Everything looks good - your deployment is live and running successfully!
|
|
257
325
|
```
|
|
258
326
|
|
|
259
|
-
|
|
260
|
-
|
|
327
|
+
**More examples:**
|
|
261
328
|
- `"Is my website deployment finished?"`
|
|
262
329
|
- `"Show me the deployment status for project-xyz"`
|
|
263
330
|
- `"Check if the production deployment succeeded"`
|
|
264
331
|
- `"What's the status of my latest deploy?"`
|
|
265
332
|
|
|
333
|
+
### Badge Usage
|
|
334
|
+
|
|
335
|
+
Add live deployment status badges to your README:
|
|
336
|
+
|
|
337
|
+
```markdown
|
|
338
|
+
# My Project
|
|
339
|
+
|
|
340
|
+

|
|
341
|
+
|
|
342
|
+
<!-- Other content -->
|
|
343
|
+
```
|
|
344
|
+
|
|
345
|
+
**Multiple platforms:**
|
|
346
|
+
```markdown
|
|
347
|
+
[](https://deploy-mcp.io)
|
|
348
|
+
[](https://deploy-mcp.io)
|
|
349
|
+
```
|
|
350
|
+
|
|
266
351
|
|
|
267
352
|
## MCP Tools Reference
|
|
268
353
|
Retrieve the latest deployment status for a project.
|
package/dist/worker.js
CHANGED
|
@@ -3,6 +3,129 @@ import {
|
|
|
3
3
|
VercelAdapter
|
|
4
4
|
} from "./chunk-QRZL43CY.js";
|
|
5
5
|
|
|
6
|
+
// src/utils/github.ts
|
|
7
|
+
async function validateRepository(user, repo) {
|
|
8
|
+
try {
|
|
9
|
+
const response = await fetch(
|
|
10
|
+
`https://api.github.com/repos/${user}/${repo}`,
|
|
11
|
+
{
|
|
12
|
+
headers: {
|
|
13
|
+
"User-Agent": "deploy-mcp/1.0",
|
|
14
|
+
Accept: "application/vnd.github.v3+json"
|
|
15
|
+
}
|
|
16
|
+
}
|
|
17
|
+
);
|
|
18
|
+
if (!response.ok) {
|
|
19
|
+
return { exists: false, isPublic: false };
|
|
20
|
+
}
|
|
21
|
+
const repoData = await response.json();
|
|
22
|
+
const isPublic = !repoData.private;
|
|
23
|
+
return {
|
|
24
|
+
exists: true,
|
|
25
|
+
isPublic,
|
|
26
|
+
name: repoData.name,
|
|
27
|
+
fullName: repoData.full_name
|
|
28
|
+
};
|
|
29
|
+
} catch {
|
|
30
|
+
return { exists: false, isPublic: false };
|
|
31
|
+
}
|
|
32
|
+
}
|
|
33
|
+
function validateParams(user, repo, platform) {
|
|
34
|
+
const validName = /^[a-zA-Z0-9._-]+$/;
|
|
35
|
+
const validPlatforms = ["vercel", "netlify", "railway"];
|
|
36
|
+
return validName.test(user) && validName.test(repo) && validPlatforms.includes(platform);
|
|
37
|
+
}
|
|
38
|
+
|
|
39
|
+
// src/server/badge.ts
|
|
40
|
+
var STATUS_COLORS = {
|
|
41
|
+
success: "brightgreen",
|
|
42
|
+
failed: "red",
|
|
43
|
+
building: "yellow",
|
|
44
|
+
error: "lightgrey",
|
|
45
|
+
unknown: "lightgrey"
|
|
46
|
+
};
|
|
47
|
+
var PLATFORM_CONFIG = {
|
|
48
|
+
vercel: {
|
|
49
|
+
label: "Vercel",
|
|
50
|
+
logo: "vercel"
|
|
51
|
+
},
|
|
52
|
+
netlify: {
|
|
53
|
+
label: "Netlify",
|
|
54
|
+
logo: "netlify"
|
|
55
|
+
},
|
|
56
|
+
railway: {
|
|
57
|
+
label: "Railway",
|
|
58
|
+
logo: "railway"
|
|
59
|
+
}
|
|
60
|
+
};
|
|
61
|
+
function validateParams2(user, repo, platform) {
|
|
62
|
+
const validPlatforms = Object.keys(PLATFORM_CONFIG);
|
|
63
|
+
return validateParams(user, repo, platform) && user.length <= 50 && repo.length <= 100 && validPlatforms.includes(platform);
|
|
64
|
+
}
|
|
65
|
+
async function getDeploymentStatus(user, repo, platform, env) {
|
|
66
|
+
if (!env.BADGE_KV) {
|
|
67
|
+
return "unknown";
|
|
68
|
+
}
|
|
69
|
+
try {
|
|
70
|
+
const key = `${user}/${repo}/${platform}`;
|
|
71
|
+
const status = await env.BADGE_KV.get(key, "text");
|
|
72
|
+
if (!status) return "unknown";
|
|
73
|
+
const validStatuses = [
|
|
74
|
+
"success",
|
|
75
|
+
"failed",
|
|
76
|
+
"building",
|
|
77
|
+
"error",
|
|
78
|
+
"unknown"
|
|
79
|
+
];
|
|
80
|
+
return validStatuses.includes(status) ? status : "unknown";
|
|
81
|
+
} catch {
|
|
82
|
+
return "error";
|
|
83
|
+
}
|
|
84
|
+
}
|
|
85
|
+
async function generateBadgeData(user, repo, platform, env) {
|
|
86
|
+
if (!validateParams2(user, repo, platform)) {
|
|
87
|
+
throw new Error("Invalid parameters");
|
|
88
|
+
}
|
|
89
|
+
const repoInfo = await validateRepository(user, repo);
|
|
90
|
+
if (!repoInfo.exists || !repoInfo.isPublic) {
|
|
91
|
+
return generateErrorBadge(platform, "Repository not found or private");
|
|
92
|
+
}
|
|
93
|
+
const status = await getDeploymentStatus(user, repo, platform, env);
|
|
94
|
+
const config = PLATFORM_CONFIG[platform];
|
|
95
|
+
const messages = {
|
|
96
|
+
success: "deployed",
|
|
97
|
+
failed: "failed",
|
|
98
|
+
building: "deploying",
|
|
99
|
+
error: "error",
|
|
100
|
+
unknown: "unknown"
|
|
101
|
+
};
|
|
102
|
+
return {
|
|
103
|
+
schemaVersion: 1,
|
|
104
|
+
label: config.label,
|
|
105
|
+
message: messages[status],
|
|
106
|
+
color: STATUS_COLORS[status]
|
|
107
|
+
};
|
|
108
|
+
}
|
|
109
|
+
function generateErrorBadge(platform, message) {
|
|
110
|
+
const config = PLATFORM_CONFIG[platform];
|
|
111
|
+
return {
|
|
112
|
+
schemaVersion: 1,
|
|
113
|
+
label: config ? config.label : platform,
|
|
114
|
+
message,
|
|
115
|
+
color: "lightgrey"
|
|
116
|
+
};
|
|
117
|
+
}
|
|
118
|
+
async function updateDeploymentStatus(user, repo, platform, status, env) {
|
|
119
|
+
if (!env.BADGE_KV) {
|
|
120
|
+
throw new Error("KV storage not configured");
|
|
121
|
+
}
|
|
122
|
+
const key = `${user}/${repo}/${platform}`;
|
|
123
|
+
await env.BADGE_KV.put(key, status, {
|
|
124
|
+
expirationTtl: 86400 * 30
|
|
125
|
+
// Expire after 30 days
|
|
126
|
+
});
|
|
127
|
+
}
|
|
128
|
+
|
|
6
129
|
// src/landing-page.ts
|
|
7
130
|
var landingPageHTML = `<!DOCTYPE html>
|
|
8
131
|
<html lang="en">
|
|
@@ -595,7 +718,8 @@ var landingPageHTML = `<!DOCTYPE html>
|
|
|
595
718
|
}
|
|
596
719
|
|
|
597
720
|
.platform.supported .platform-icon {
|
|
598
|
-
background:
|
|
721
|
+
background: rgba(255, 255, 255, 0.1);
|
|
722
|
+
border: 2px solid var(--accent-green);
|
|
599
723
|
}
|
|
600
724
|
|
|
601
725
|
.platform.coming-soon {
|
|
@@ -896,28 +1020,46 @@ var landingPageHTML = `<!DOCTYPE html>
|
|
|
896
1020
|
|
|
897
1021
|
<div class="badge-showcase">
|
|
898
1022
|
<div class="badge-preview">
|
|
899
|
-
<
|
|
900
|
-
|
|
901
|
-
<
|
|
902
|
-
|
|
903
|
-
|
|
904
|
-
|
|
905
|
-
<
|
|
906
|
-
|
|
907
|
-
|
|
908
|
-
|
|
909
|
-
<
|
|
1023
|
+
<h3 style="margin-bottom: 1rem; color: var(--text-primary); font-size: 1.1rem;">Live Examples:</h3>
|
|
1024
|
+
<div style="display: flex; flex-direction: column; gap: 0.75rem; margin-bottom: 1.5rem;">
|
|
1025
|
+
<div style="display: flex; align-items: center;">
|
|
1026
|
+
<img src="https://img.shields.io/endpoint?url=https://deploy-mcp.io/badge/microsoft/vscode/vercel" alt="Vercel Deploy Status" style="margin-right: 0.75rem;" />
|
|
1027
|
+
<span style="color: var(--text-secondary); font-size: 0.9rem;">microsoft/vscode on Vercel</span>
|
|
1028
|
+
</div>
|
|
1029
|
+
<div style="display: flex; align-items: center;">
|
|
1030
|
+
<img src="https://img.shields.io/endpoint?url=https://deploy-mcp.io/badge/facebook/react/vercel" alt="Netlify Deploy Status" style="margin-right: 0.75rem;" />
|
|
1031
|
+
<span style="color: var(--text-secondary); font-size: 0.9rem;">facebook/react on Netlify</span>
|
|
1032
|
+
</div>
|
|
1033
|
+
<div style="display: flex; align-items: center;">
|
|
1034
|
+
<img src="https://img.shields.io/endpoint?url=https://deploy-mcp.io/badge/angular/angular/vercel" alt="Railway Deploy Status" style="margin-right: 0.75rem;" />
|
|
1035
|
+
<span style="color: var(--text-secondary); font-size: 0.9rem;">angular/angular on Railway</span>
|
|
1036
|
+
</div>
|
|
910
1037
|
</div>
|
|
911
1038
|
</div>
|
|
912
1039
|
<div class="code-wrapper multiline">
|
|
913
1040
|
<div class="code-header">
|
|
914
|
-
<button class="copy-btn" onclick="copyToClipboard('[](https://deploy-mcp.io)', event)">Copy</button>
|
|
915
1042
|
</div>
|
|
916
|
-
<div class="code-content">[](https://deploy-mcp.io)</div>
|
|
917
1044
|
</div>
|
|
918
1045
|
</div>
|
|
919
1046
|
<div style="text-align: center; margin-top: 1.5rem;">
|
|
920
|
-
<p style="color: var(--accent-
|
|
1047
|
+
<p style="color: var(--accent-green); font-size: 1rem; font-weight: 600;">\u2705 Now Available</p>
|
|
1048
|
+
<p style="color: var(--text-secondary); font-size: 0.9rem; margin-top: 0.5rem;">
|
|
1049
|
+
Supports Vercel \u2022 Netlify and Railway coming soon
|
|
1050
|
+
</p>
|
|
1051
|
+
</div>
|
|
1052
|
+
|
|
1053
|
+
<div style="margin-top: 2rem; padding: 1.5rem; background: var(--bg-secondary); border-radius: 8px; border-left: 3px solid var(--accent-blue);">
|
|
1054
|
+
<h4 style="margin: 0 0 1rem 0; color: var(--text-primary); font-size: 1rem;">Quick Setup:</h4>
|
|
1055
|
+
<ol style="margin: 0; padding-left: 1.5rem; color: var(--text-secondary); line-height: 1.6;">
|
|
1056
|
+
<li>Add badge to your README (replace user/repo in URL above)</li>
|
|
1057
|
+
<li>Configure webhook in your deployment platform</li>
|
|
1058
|
+
<li>Badge updates automatically on each deployment</li>
|
|
1059
|
+
</ol>
|
|
1060
|
+
<p style="margin: 1rem 0 0 0; color: var(--text-secondary); font-size: 0.9rem;">
|
|
1061
|
+
\u{1F4D6} <strong>Detailed setup instructions:</strong> <a href="https://github.com/alexpota/deploy-mcp#deployment-status-badges" style="color: var(--accent-blue); text-decoration: none;">View README</a>
|
|
1062
|
+
</p>
|
|
921
1063
|
</div>
|
|
922
1064
|
</div>
|
|
923
1065
|
|
|
@@ -929,25 +1071,33 @@ var landingPageHTML = `<!DOCTYPE html>
|
|
|
929
1071
|
<div class="platforms">
|
|
930
1072
|
<div class="platform supported">
|
|
931
1073
|
<div class="platform-status"></div>
|
|
932
|
-
<div class="platform-icon">
|
|
1074
|
+
<div class="platform-icon">
|
|
1075
|
+
<img src="https://vercel.com/favicon.ico" alt="Vercel" style="width: 24px; height: 24px;">
|
|
1076
|
+
</div>
|
|
933
1077
|
<h3>Vercel</h3>
|
|
934
1078
|
<p>Full deployment tracking with real-time status updates</p>
|
|
935
1079
|
</div>
|
|
936
1080
|
<div class="platform coming-soon">
|
|
937
1081
|
<div class="platform-status"></div>
|
|
938
|
-
<div class="platform-icon">
|
|
1082
|
+
<div class="platform-icon">
|
|
1083
|
+
<img src="https://www.netlify.com/favicon.ico" alt="Netlify" style="width: 24px; height: 24px;">
|
|
1084
|
+
</div>
|
|
939
1085
|
<h3>Netlify</h3>
|
|
940
1086
|
<p>Coming in next release</p>
|
|
941
1087
|
</div>
|
|
942
1088
|
<div class="platform coming-soon">
|
|
943
1089
|
<div class="platform-status"></div>
|
|
944
|
-
<div class="platform-icon">
|
|
1090
|
+
<div class="platform-icon">
|
|
1091
|
+
<img src="https://railway.app/favicon.ico" alt="Railway" style="width: 24px; height: 24px;">
|
|
1092
|
+
</div>
|
|
945
1093
|
<h3>Railway</h3>
|
|
946
1094
|
<p>On the roadmap</p>
|
|
947
1095
|
</div>
|
|
948
1096
|
<div class="platform coming-soon">
|
|
949
1097
|
<div class="platform-status"></div>
|
|
950
|
-
<div class="platform-icon">
|
|
1098
|
+
<div class="platform-icon">
|
|
1099
|
+
<img src="https://us1.discourse-cdn.com/flex016/uploads/render/original/2X/a/ad2cd49c57c27455f695b61f3f8a01571697b336.svg" alt="Render" style="width: 24px; height: 24px;">
|
|
1100
|
+
</div>
|
|
951
1101
|
<h3>Render</h3>
|
|
952
1102
|
<p>On the roadmap</p>
|
|
953
1103
|
</div>
|
|
@@ -1153,6 +1303,154 @@ var landingPageHTML = `<!DOCTYPE html>
|
|
|
1153
1303
|
</body>
|
|
1154
1304
|
</html>`;
|
|
1155
1305
|
|
|
1306
|
+
// src/server/webhook.ts
|
|
1307
|
+
async function validateWebhookSignature(request, platform, env) {
|
|
1308
|
+
if (platform === "vercel") {
|
|
1309
|
+
const signature = request.headers.get("x-vercel-signature");
|
|
1310
|
+
if (!signature) {
|
|
1311
|
+
return false;
|
|
1312
|
+
}
|
|
1313
|
+
const secret = env.VERCEL_WEBHOOK_SECRET;
|
|
1314
|
+
if (!secret) {
|
|
1315
|
+
console.log(
|
|
1316
|
+
"VERCEL_WEBHOOK_SECRET not configured, skipping signature validation"
|
|
1317
|
+
);
|
|
1318
|
+
return true;
|
|
1319
|
+
}
|
|
1320
|
+
try {
|
|
1321
|
+
const body = await request.clone().text();
|
|
1322
|
+
const encoder = new TextEncoder();
|
|
1323
|
+
const keyData = encoder.encode(secret);
|
|
1324
|
+
const bodyData = encoder.encode(body);
|
|
1325
|
+
const cryptoKey = await crypto.subtle.importKey(
|
|
1326
|
+
"raw",
|
|
1327
|
+
keyData,
|
|
1328
|
+
{ name: "HMAC", hash: "SHA-256" },
|
|
1329
|
+
false,
|
|
1330
|
+
["sign"]
|
|
1331
|
+
);
|
|
1332
|
+
const signatureBuffer = await crypto.subtle.sign(
|
|
1333
|
+
"HMAC",
|
|
1334
|
+
cryptoKey,
|
|
1335
|
+
bodyData
|
|
1336
|
+
);
|
|
1337
|
+
const computedSignature = Array.from(new Uint8Array(signatureBuffer)).map((b) => b.toString(16).padStart(2, "0")).join("");
|
|
1338
|
+
const expectedSignature = `sha256=${computedSignature}`;
|
|
1339
|
+
return signature === expectedSignature;
|
|
1340
|
+
} catch (error) {
|
|
1341
|
+
console.error("Webhook signature validation error:", error);
|
|
1342
|
+
return false;
|
|
1343
|
+
}
|
|
1344
|
+
}
|
|
1345
|
+
return true;
|
|
1346
|
+
}
|
|
1347
|
+
function mapDeploymentStatus(platform, payload) {
|
|
1348
|
+
switch (platform) {
|
|
1349
|
+
case "vercel": {
|
|
1350
|
+
const vercelPayload = payload;
|
|
1351
|
+
switch (vercelPayload.deployment?.state) {
|
|
1352
|
+
case "READY":
|
|
1353
|
+
return "success";
|
|
1354
|
+
case "BUILDING":
|
|
1355
|
+
return "building";
|
|
1356
|
+
case "ERROR":
|
|
1357
|
+
return "failed";
|
|
1358
|
+
case "CANCELED":
|
|
1359
|
+
return "failed";
|
|
1360
|
+
default:
|
|
1361
|
+
return "unknown";
|
|
1362
|
+
}
|
|
1363
|
+
}
|
|
1364
|
+
case "netlify": {
|
|
1365
|
+
const netlifyPayload = payload;
|
|
1366
|
+
switch (netlifyPayload.state) {
|
|
1367
|
+
case "ready":
|
|
1368
|
+
return "success";
|
|
1369
|
+
case "building":
|
|
1370
|
+
return "building";
|
|
1371
|
+
case "error":
|
|
1372
|
+
return "failed";
|
|
1373
|
+
default:
|
|
1374
|
+
return "unknown";
|
|
1375
|
+
}
|
|
1376
|
+
}
|
|
1377
|
+
case "railway":
|
|
1378
|
+
return "unknown";
|
|
1379
|
+
default:
|
|
1380
|
+
return "unknown";
|
|
1381
|
+
}
|
|
1382
|
+
}
|
|
1383
|
+
async function handleWebhook(request, user, repo, platform, env) {
|
|
1384
|
+
console.log(`Processing webhook: ${user}/${repo}/${platform}`);
|
|
1385
|
+
if (!validateParams(user, repo, platform)) {
|
|
1386
|
+
return {
|
|
1387
|
+
success: false,
|
|
1388
|
+
message: "Invalid parameters",
|
|
1389
|
+
error: "User, repo, or platform format is invalid"
|
|
1390
|
+
};
|
|
1391
|
+
}
|
|
1392
|
+
const repoValidation = await validateRepository(user, repo);
|
|
1393
|
+
if (!repoValidation.exists) {
|
|
1394
|
+
return {
|
|
1395
|
+
success: false,
|
|
1396
|
+
message: "Repository not found",
|
|
1397
|
+
error: `Repository ${user}/${repo} does not exist or is not accessible`
|
|
1398
|
+
};
|
|
1399
|
+
}
|
|
1400
|
+
if (!repoValidation.isPublic) {
|
|
1401
|
+
return {
|
|
1402
|
+
success: false,
|
|
1403
|
+
message: "Private repository",
|
|
1404
|
+
error: "Badge updates are only supported for public repositories"
|
|
1405
|
+
};
|
|
1406
|
+
}
|
|
1407
|
+
const isValidSignature = await validateWebhookSignature(
|
|
1408
|
+
request,
|
|
1409
|
+
platform,
|
|
1410
|
+
env
|
|
1411
|
+
);
|
|
1412
|
+
if (!isValidSignature) {
|
|
1413
|
+
return {
|
|
1414
|
+
success: false,
|
|
1415
|
+
message: "Invalid webhook",
|
|
1416
|
+
error: "Webhook validation failed"
|
|
1417
|
+
};
|
|
1418
|
+
}
|
|
1419
|
+
let payload;
|
|
1420
|
+
try {
|
|
1421
|
+
payload = await request.json();
|
|
1422
|
+
console.log(
|
|
1423
|
+
`${platform} webhook payload:`,
|
|
1424
|
+
JSON.stringify(payload, null, 2)
|
|
1425
|
+
);
|
|
1426
|
+
} catch {
|
|
1427
|
+
return {
|
|
1428
|
+
success: false,
|
|
1429
|
+
message: "Invalid JSON payload",
|
|
1430
|
+
error: "Could not parse webhook payload"
|
|
1431
|
+
};
|
|
1432
|
+
}
|
|
1433
|
+
const deploymentStatus = mapDeploymentStatus(platform, payload);
|
|
1434
|
+
console.log(`Mapped deployment status: ${deploymentStatus}`);
|
|
1435
|
+
try {
|
|
1436
|
+
await updateDeploymentStatus(user, repo, platform, deploymentStatus, env);
|
|
1437
|
+
console.log(
|
|
1438
|
+
`Updated KV storage: ${user}/${repo}/${platform} = ${deploymentStatus}`
|
|
1439
|
+
);
|
|
1440
|
+
return {
|
|
1441
|
+
success: true,
|
|
1442
|
+
message: `Deployment status updated to: ${deploymentStatus}`
|
|
1443
|
+
};
|
|
1444
|
+
} catch (error) {
|
|
1445
|
+
console.error("Failed to update KV storage:", error);
|
|
1446
|
+
return {
|
|
1447
|
+
success: false,
|
|
1448
|
+
message: "Failed to update deployment status",
|
|
1449
|
+
error: error instanceof Error ? error.message : "Unknown KV error"
|
|
1450
|
+
};
|
|
1451
|
+
}
|
|
1452
|
+
}
|
|
1453
|
+
|
|
1156
1454
|
// src/worker.ts
|
|
1157
1455
|
function getAdapters(_env) {
|
|
1158
1456
|
const adapters = /* @__PURE__ */ new Map();
|
|
@@ -1189,6 +1487,77 @@ var worker_default = {
|
|
|
1189
1487
|
);
|
|
1190
1488
|
}
|
|
1191
1489
|
}
|
|
1490
|
+
if (request.method === "GET" && url.pathname.startsWith("/badge/")) {
|
|
1491
|
+
try {
|
|
1492
|
+
const pathParts = url.pathname.split("/");
|
|
1493
|
+
if (pathParts.length !== 5) {
|
|
1494
|
+
const errorBadge = generateErrorBadge("deploy", "Invalid URL format");
|
|
1495
|
+
return new Response(JSON.stringify(errorBadge), {
|
|
1496
|
+
status: 400,
|
|
1497
|
+
headers: {
|
|
1498
|
+
"Content-Type": "application/json",
|
|
1499
|
+
"Cache-Control": "public, max-age=60",
|
|
1500
|
+
"Access-Control-Allow-Origin": "*"
|
|
1501
|
+
}
|
|
1502
|
+
});
|
|
1503
|
+
}
|
|
1504
|
+
const [, , user, repo, platform] = pathParts;
|
|
1505
|
+
const badgeData = await generateBadgeData(user, repo, platform, env);
|
|
1506
|
+
return new Response(JSON.stringify(badgeData), {
|
|
1507
|
+
headers: {
|
|
1508
|
+
"Content-Type": "application/json",
|
|
1509
|
+
"Cache-Control": "public, max-age=300",
|
|
1510
|
+
// Cache for 5 minutes
|
|
1511
|
+
"Access-Control-Allow-Origin": "*"
|
|
1512
|
+
}
|
|
1513
|
+
});
|
|
1514
|
+
} catch (error) {
|
|
1515
|
+
const errorBadge = generateErrorBadge(
|
|
1516
|
+
"deploy",
|
|
1517
|
+
error instanceof Error ? error.message : "Unknown error"
|
|
1518
|
+
);
|
|
1519
|
+
return new Response(JSON.stringify(errorBadge), {
|
|
1520
|
+
status: 500,
|
|
1521
|
+
headers: {
|
|
1522
|
+
"Content-Type": "application/json",
|
|
1523
|
+
"Cache-Control": "public, max-age=60",
|
|
1524
|
+
"Access-Control-Allow-Origin": "*"
|
|
1525
|
+
}
|
|
1526
|
+
});
|
|
1527
|
+
}
|
|
1528
|
+
}
|
|
1529
|
+
if (request.method === "POST" && url.pathname.startsWith("/webhook/")) {
|
|
1530
|
+
try {
|
|
1531
|
+
const pathParts = url.pathname.split("/");
|
|
1532
|
+
if (pathParts.length !== 5) {
|
|
1533
|
+
return new Response("Invalid webhook URL format", { status: 400 });
|
|
1534
|
+
}
|
|
1535
|
+
const [, , user, repo, platform] = pathParts;
|
|
1536
|
+
const result = await handleWebhook(request, user, repo, platform, env);
|
|
1537
|
+
return new Response(JSON.stringify(result), {
|
|
1538
|
+
status: result.success ? 200 : 400,
|
|
1539
|
+
headers: {
|
|
1540
|
+
"Content-Type": "application/json",
|
|
1541
|
+
"Access-Control-Allow-Origin": "*"
|
|
1542
|
+
}
|
|
1543
|
+
});
|
|
1544
|
+
} catch (error) {
|
|
1545
|
+
console.error("Webhook error:", error);
|
|
1546
|
+
return new Response(
|
|
1547
|
+
JSON.stringify({
|
|
1548
|
+
success: false,
|
|
1549
|
+
error: error instanceof Error ? error.message : "Unknown error"
|
|
1550
|
+
}),
|
|
1551
|
+
{
|
|
1552
|
+
status: 500,
|
|
1553
|
+
headers: {
|
|
1554
|
+
"Content-Type": "application/json",
|
|
1555
|
+
"Access-Control-Allow-Origin": "*"
|
|
1556
|
+
}
|
|
1557
|
+
}
|
|
1558
|
+
);
|
|
1559
|
+
}
|
|
1560
|
+
}
|
|
1192
1561
|
if (request.method === "OPTIONS") {
|
|
1193
1562
|
return new Response(null, {
|
|
1194
1563
|
headers: {
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "deploy-mcp",
|
|
3
|
-
"version": "0.
|
|
3
|
+
"version": "0.2.0",
|
|
4
4
|
"description": "Universal deployment tracker for AI assistants",
|
|
5
5
|
"keywords": [
|
|
6
6
|
"mcp",
|
|
@@ -37,6 +37,7 @@
|
|
|
37
37
|
"dev:worker": "wrangler dev",
|
|
38
38
|
"build": "tsup src/index.ts src/worker.ts --format esm --platform node --target node22",
|
|
39
39
|
"test": "vitest",
|
|
40
|
+
"test:ci": "vitest --run",
|
|
40
41
|
"lint": "eslint src --ext .ts",
|
|
41
42
|
"lint:fix": "eslint src --ext .ts --fix",
|
|
42
43
|
"type-check": "tsc --noEmit",
|
|
@@ -52,7 +53,7 @@
|
|
|
52
53
|
"zod": "^3.24.1"
|
|
53
54
|
},
|
|
54
55
|
"devDependencies": {
|
|
55
|
-
"@cloudflare/workers-types": "^4.
|
|
56
|
+
"@cloudflare/workers-types": "^4.20250803.0",
|
|
56
57
|
"@types/node": "^22.10.5",
|
|
57
58
|
"@typescript-eslint/eslint-plugin": "^8.38.0",
|
|
58
59
|
"@typescript-eslint/parser": "^8.38.0",
|
|
@@ -64,8 +65,8 @@
|
|
|
64
65
|
"prettier": "^3.6.2",
|
|
65
66
|
"tsup": "^8.3.5",
|
|
66
67
|
"tsx": "^4.19.2",
|
|
67
|
-
"typescript": "^5.
|
|
68
|
-
"vitest": "^2.
|
|
68
|
+
"typescript": "^5.8.3",
|
|
69
|
+
"vitest": "^3.2.4",
|
|
69
70
|
"wrangler": "^4.27.0"
|
|
70
71
|
},
|
|
71
72
|
"engines": {
|