@vercel/slack-bolt 1.3.1 → 1.4.1
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 +84 -8
- package/dist/cli.d.mts +1 -1
- package/dist/cli.d.ts +1 -1
- package/dist/cli.js +357 -507
- package/dist/cli.js.map +1 -1
- package/dist/cli.mjs +385 -497
- package/dist/cli.mjs.map +1 -1
- package/dist/index.d.mts +181 -90
- package/dist/index.d.ts +181 -90
- package/dist/index.js +491 -367
- package/dist/index.js.map +1 -1
- package/dist/index.mjs +489 -365
- package/dist/index.mjs.map +1 -1
- package/dist/preview-DAWKwXt1.mjs +689 -0
- package/dist/preview-DAWKwXt1.mjs.map +1 -0
- package/dist/preview-DeqpNNn_.js +794 -0
- package/dist/preview-DeqpNNn_.js.map +1 -0
- package/dist/preview.d.mts +47 -44
- package/dist/preview.d.ts +47 -44
- package/dist/preview.js +3 -13
- package/dist/preview.mjs +2 -6
- package/package.json +9 -8
- package/dist/chunk-B5E7QPZP.mjs +0 -11
- package/dist/chunk-B5E7QPZP.mjs.map +0 -1
- package/dist/chunk-F7WJK2MS.mjs +0 -980
- package/dist/chunk-F7WJK2MS.mjs.map +0 -1
- package/dist/chunk-QHMZVK6J.js +0 -14
- package/dist/chunk-QHMZVK6J.js.map +0 -1
- package/dist/chunk-VI33CCYY.js +0 -1003
- package/dist/chunk-VI33CCYY.js.map +0 -1
- package/dist/preview.js.map +0 -1
- package/dist/preview.mjs.map +0 -1
|
@@ -0,0 +1,689 @@
|
|
|
1
|
+
import fs from "node:fs";
|
|
2
|
+
import path from "node:path";
|
|
3
|
+
import { parse, stringify } from "yaml";
|
|
4
|
+
import crypto from "node:crypto";
|
|
5
|
+
//#region \0rolldown/runtime.js
|
|
6
|
+
var __esmMin = (fn, res) => () => (fn && (res = fn(fn = 0)), res);
|
|
7
|
+
var __commonJSMin = (cb, mod) => () => (mod || cb((mod = { exports: {} }).exports, mod), mod.exports);
|
|
8
|
+
//#endregion
|
|
9
|
+
//#region src/internal/manifest/index.ts
|
|
10
|
+
function rewriteUrl(originalUrl, branchUrl, bypassSecret) {
|
|
11
|
+
const pathStart = originalUrl.indexOf("/", originalUrl.indexOf("//") + 2);
|
|
12
|
+
const pathAndQuery = pathStart !== -1 ? originalUrl.slice(pathStart) : "/";
|
|
13
|
+
const url = new URL(pathAndQuery, `https://${branchUrl}`);
|
|
14
|
+
if (bypassSecret) url.searchParams.set("x-vercel-protection-bypass", bypassSecret);
|
|
15
|
+
return url.toString();
|
|
16
|
+
}
|
|
17
|
+
function createManifestDescription(params) {
|
|
18
|
+
const shortSha = params.commitSha?.slice(0, 7) ?? "unknown";
|
|
19
|
+
const safeCommitMsg = params.commitMessage ?? "";
|
|
20
|
+
const safeCommitAuthor = params.commitAuthor ?? "unknown";
|
|
21
|
+
const deploymentInfo = `\n:globe_with_meridians: *Deployment URL:* ${params.branchUrl}\n:seedling: *Branch:* ${params.branch}\n:technologist: *Commit:* ${shortSha} ${safeCommitMsg}\n:bust_in_silhouette: *Last updated by:* ${safeCommitAuthor}\n\n_Automatically created by ▲ Vercel_\n`;
|
|
22
|
+
const maxLongDesc = 4e3;
|
|
23
|
+
const combined = params.existingDescription + deploymentInfo;
|
|
24
|
+
let longDescription;
|
|
25
|
+
if (combined.length > maxLongDesc) {
|
|
26
|
+
const available = Math.max(0, maxLongDesc - deploymentInfo.length);
|
|
27
|
+
longDescription = (params.existingDescription.slice(0, available) + deploymentInfo).slice(0, maxLongDesc);
|
|
28
|
+
} else longDescription = combined;
|
|
29
|
+
const maxDisplayName = 35;
|
|
30
|
+
const cleanBranch = params.branch.replace(/^refs\/heads\//, "").replace(/\//g, "-");
|
|
31
|
+
let displayName = `${params.existingName} (${cleanBranch})`;
|
|
32
|
+
if (displayName.length > maxDisplayName) {
|
|
33
|
+
const prefix = `${params.existingName} (`;
|
|
34
|
+
const suffix = ")";
|
|
35
|
+
const availableForBranch = maxDisplayName - prefix.length - 1;
|
|
36
|
+
displayName = availableForBranch > 0 ? `${prefix}${cleanBranch.slice(0, availableForBranch)}${suffix}` : displayName.slice(0, maxDisplayName);
|
|
37
|
+
}
|
|
38
|
+
return {
|
|
39
|
+
longDescription,
|
|
40
|
+
displayName
|
|
41
|
+
};
|
|
42
|
+
}
|
|
43
|
+
function createNewManifest(params) {
|
|
44
|
+
const manifest = structuredClone(params.originalManifest);
|
|
45
|
+
if (manifest.settings?.event_subscriptions?.request_url) manifest.settings.event_subscriptions.request_url = rewriteUrl(manifest.settings.event_subscriptions.request_url, params.branchUrl, params.bypassSecret);
|
|
46
|
+
if (manifest.settings?.interactivity?.request_url) manifest.settings.interactivity.request_url = rewriteUrl(manifest.settings.interactivity.request_url, params.branchUrl, params.bypassSecret);
|
|
47
|
+
if (manifest.features?.slash_commands) {
|
|
48
|
+
for (const cmd of manifest.features.slash_commands) if (cmd.url) cmd.url = rewriteUrl(cmd.url, params.branchUrl, params.bypassSecret);
|
|
49
|
+
}
|
|
50
|
+
if (manifest.oauth_config?.redirect_urls) manifest.oauth_config.redirect_urls = manifest.oauth_config.redirect_urls.map((originalUrl) => rewriteUrl(originalUrl, params.branchUrl));
|
|
51
|
+
const { longDescription, displayName } = createManifestDescription({
|
|
52
|
+
existingDescription: manifest.display_information.long_description ?? "",
|
|
53
|
+
existingName: manifest.features?.bot_user?.display_name ?? manifest.display_information.name,
|
|
54
|
+
branchUrl: params.branchUrl,
|
|
55
|
+
branch: params.branch,
|
|
56
|
+
commitSha: params.commitSha,
|
|
57
|
+
commitMessage: params.commitMessage,
|
|
58
|
+
commitAuthor: params.commitAuthor
|
|
59
|
+
});
|
|
60
|
+
manifest.display_information.long_description = longDescription;
|
|
61
|
+
manifest.display_information.name = displayName;
|
|
62
|
+
if (manifest.features?.bot_user) manifest.features.bot_user.display_name = displayName;
|
|
63
|
+
return manifest;
|
|
64
|
+
}
|
|
65
|
+
var init_manifest = __esmMin((() => {}));
|
|
66
|
+
//#endregion
|
|
67
|
+
//#region src/internal/manifest/parse.ts
|
|
68
|
+
function isYaml(filePath) {
|
|
69
|
+
const ext = path.extname(filePath).toLowerCase();
|
|
70
|
+
return ext === ".yaml" || ext === ".yml";
|
|
71
|
+
}
|
|
72
|
+
function parseManifest(raw, filePath) {
|
|
73
|
+
if (isYaml(filePath)) return parse(raw);
|
|
74
|
+
return JSON.parse(raw);
|
|
75
|
+
}
|
|
76
|
+
function stringifyManifest(manifest, filePath) {
|
|
77
|
+
if (isYaml(filePath)) return stringify(manifest);
|
|
78
|
+
return JSON.stringify(manifest, null, 2);
|
|
79
|
+
}
|
|
80
|
+
var init_parse = __esmMin((() => {}));
|
|
81
|
+
//#endregion
|
|
82
|
+
//#region src/internal/vercel/errors.ts
|
|
83
|
+
var HTTPError;
|
|
84
|
+
var init_errors$1 = __esmMin((() => {
|
|
85
|
+
HTTPError = class HTTPError extends Error {
|
|
86
|
+
constructor(message, status, statusText, body) {
|
|
87
|
+
const parts = [`${message}: ${status} ${statusText}`];
|
|
88
|
+
if (body) parts.push(body);
|
|
89
|
+
super(parts.join(" - "));
|
|
90
|
+
this.status = status;
|
|
91
|
+
this.statusText = statusText;
|
|
92
|
+
this.body = body;
|
|
93
|
+
}
|
|
94
|
+
static async fromResponse(message, response) {
|
|
95
|
+
let body;
|
|
96
|
+
try {
|
|
97
|
+
const text = await response.text();
|
|
98
|
+
if (text) body = text;
|
|
99
|
+
} catch {}
|
|
100
|
+
return new HTTPError(message, response.status, response.statusText, body);
|
|
101
|
+
}
|
|
102
|
+
};
|
|
103
|
+
}));
|
|
104
|
+
//#endregion
|
|
105
|
+
//#region src/internal/slack/errors.ts
|
|
106
|
+
var SlackManifestCreateError, SlackManifestUpdateError, SlackManifestExportError;
|
|
107
|
+
var init_errors = __esmMin((() => {
|
|
108
|
+
SlackManifestCreateError = class extends Error {
|
|
109
|
+
constructor(message, errors) {
|
|
110
|
+
super(message);
|
|
111
|
+
this.errors = errors;
|
|
112
|
+
}
|
|
113
|
+
};
|
|
114
|
+
SlackManifestUpdateError = class extends Error {
|
|
115
|
+
constructor(message, errors) {
|
|
116
|
+
super(message);
|
|
117
|
+
this.errors = errors;
|
|
118
|
+
}
|
|
119
|
+
};
|
|
120
|
+
SlackManifestExportError = class extends Error {};
|
|
121
|
+
}));
|
|
122
|
+
//#endregion
|
|
123
|
+
//#region src/internal/slack/index.ts
|
|
124
|
+
async function createSlackApp({ token, manifest }) {
|
|
125
|
+
const response = await fetch("https://slack.com/api/apps.manifest.create", {
|
|
126
|
+
method: "POST",
|
|
127
|
+
headers: {
|
|
128
|
+
Authorization: `Bearer ${token}`,
|
|
129
|
+
"Content-Type": "application/json; charset=utf-8"
|
|
130
|
+
},
|
|
131
|
+
body: JSON.stringify({ manifest: JSON.stringify(manifest) })
|
|
132
|
+
});
|
|
133
|
+
if (!response.ok) throw new HTTPError("Failed to create Slack app", response.status, response.statusText);
|
|
134
|
+
const data = await response.json();
|
|
135
|
+
if (!data.ok) throw new SlackManifestCreateError(data?.error ?? "Unknown error", data?.errors);
|
|
136
|
+
return data;
|
|
137
|
+
}
|
|
138
|
+
async function updateSlackApp({ token, appId, manifest }) {
|
|
139
|
+
const response = await fetch("https://slack.com/api/apps.manifest.update", {
|
|
140
|
+
method: "POST",
|
|
141
|
+
headers: {
|
|
142
|
+
Authorization: `Bearer ${token}`,
|
|
143
|
+
"Content-Type": "application/json; charset=utf-8"
|
|
144
|
+
},
|
|
145
|
+
body: JSON.stringify({
|
|
146
|
+
app_id: appId,
|
|
147
|
+
manifest: JSON.stringify(manifest)
|
|
148
|
+
})
|
|
149
|
+
});
|
|
150
|
+
if (!response.ok) throw new HTTPError("Failed to update Slack app", response.status, response.statusText);
|
|
151
|
+
const data = await response.json();
|
|
152
|
+
if (!data.ok) throw new SlackManifestUpdateError(data?.error ?? "Unknown error", data?.errors);
|
|
153
|
+
return data;
|
|
154
|
+
}
|
|
155
|
+
async function exportSlackApp({ token, appId }) {
|
|
156
|
+
const response = await fetch("https://slack.com/api/apps.manifest.export", {
|
|
157
|
+
method: "POST",
|
|
158
|
+
headers: {
|
|
159
|
+
Authorization: `Bearer ${token}`,
|
|
160
|
+
"Content-Type": "application/json; charset=utf-8"
|
|
161
|
+
},
|
|
162
|
+
body: JSON.stringify({ app_id: appId })
|
|
163
|
+
});
|
|
164
|
+
if (!response.ok) throw new HTTPError("Failed to export Slack app", response.status, response.statusText);
|
|
165
|
+
const data = await response.json();
|
|
166
|
+
if (!data.ok) throw new SlackManifestExportError(data?.error ?? "Unknown error");
|
|
167
|
+
return data;
|
|
168
|
+
}
|
|
169
|
+
async function upsertSlackApp({ token, appId, manifest }) {
|
|
170
|
+
if (appId) try {
|
|
171
|
+
await exportSlackApp({
|
|
172
|
+
token,
|
|
173
|
+
appId
|
|
174
|
+
});
|
|
175
|
+
return {
|
|
176
|
+
isNew: false,
|
|
177
|
+
app: await updateSlackApp({
|
|
178
|
+
token,
|
|
179
|
+
appId,
|
|
180
|
+
manifest
|
|
181
|
+
})
|
|
182
|
+
};
|
|
183
|
+
} catch {}
|
|
184
|
+
return {
|
|
185
|
+
isNew: true,
|
|
186
|
+
app: await createSlackApp({
|
|
187
|
+
token,
|
|
188
|
+
manifest
|
|
189
|
+
})
|
|
190
|
+
};
|
|
191
|
+
}
|
|
192
|
+
async function deleteSlackApp({ token, appId }) {
|
|
193
|
+
const response = await fetch("https://slack.com/api/apps.manifest.delete", {
|
|
194
|
+
method: "POST",
|
|
195
|
+
headers: {
|
|
196
|
+
Authorization: `Bearer ${token}`,
|
|
197
|
+
"Content-Type": "application/json; charset=utf-8"
|
|
198
|
+
},
|
|
199
|
+
body: JSON.stringify({ app_id: appId })
|
|
200
|
+
});
|
|
201
|
+
if (!response.ok) throw new HTTPError("Failed to delete Slack app", response.status, response.statusText);
|
|
202
|
+
const data = await response.json();
|
|
203
|
+
if (!data.ok) throw new Error(data.error ?? "Unknown error");
|
|
204
|
+
}
|
|
205
|
+
async function rotateConfigToken({ refreshToken }) {
|
|
206
|
+
const response = await fetch("https://slack.com/api/tooling.tokens.rotate", {
|
|
207
|
+
method: "POST",
|
|
208
|
+
headers: { "Content-Type": "application/x-www-form-urlencoded" },
|
|
209
|
+
body: new URLSearchParams({ refresh_token: refreshToken })
|
|
210
|
+
});
|
|
211
|
+
if (!response.ok) throw new HTTPError("Failed to rotate configuration token", response.status, response.statusText);
|
|
212
|
+
const data = await response.json();
|
|
213
|
+
if (!data.ok || !data.token || !data.refresh_token) throw new Error(data.error ?? "Unknown error rotating token");
|
|
214
|
+
return {
|
|
215
|
+
token: data.token,
|
|
216
|
+
refreshToken: data.refresh_token,
|
|
217
|
+
exp: data.exp ?? 0
|
|
218
|
+
};
|
|
219
|
+
}
|
|
220
|
+
async function authTest({ token }) {
|
|
221
|
+
const response = await fetch("https://slack.com/api/auth.test", {
|
|
222
|
+
method: "POST",
|
|
223
|
+
headers: {
|
|
224
|
+
Authorization: `Bearer ${token}`,
|
|
225
|
+
"Content-Type": "application/json; charset=utf-8"
|
|
226
|
+
}
|
|
227
|
+
});
|
|
228
|
+
if (!response.ok) throw new HTTPError("Auth test failed", response.status, response.statusText);
|
|
229
|
+
const data = await response.json();
|
|
230
|
+
if (!data.ok) throw new Error(data.error ?? "Unknown error");
|
|
231
|
+
return {
|
|
232
|
+
userId: data.user_id ?? "",
|
|
233
|
+
teamId: data.team_id ?? ""
|
|
234
|
+
};
|
|
235
|
+
}
|
|
236
|
+
async function verifyServiceTokenAccess(params) {
|
|
237
|
+
try {
|
|
238
|
+
await exportSlackApp({
|
|
239
|
+
token: params.serviceToken,
|
|
240
|
+
appId: params.appId
|
|
241
|
+
});
|
|
242
|
+
return { hasAccess: true };
|
|
243
|
+
} catch {
|
|
244
|
+
return { hasAccess: false };
|
|
245
|
+
}
|
|
246
|
+
}
|
|
247
|
+
async function installApp(params) {
|
|
248
|
+
const { serviceToken, appId, botScopes, outgoingDomains } = params;
|
|
249
|
+
if (!serviceToken) return { status: "missing_service_token" };
|
|
250
|
+
const response = await fetch("https://slack.com/api/apps.developerInstall", {
|
|
251
|
+
method: "POST",
|
|
252
|
+
headers: {
|
|
253
|
+
Authorization: `Bearer ${serviceToken}`,
|
|
254
|
+
"Content-Type": "application/json; charset=utf-8"
|
|
255
|
+
},
|
|
256
|
+
body: JSON.stringify({
|
|
257
|
+
app_id: appId,
|
|
258
|
+
bot_scopes: botScopes,
|
|
259
|
+
outgoing_domains: outgoingDomains ?? []
|
|
260
|
+
})
|
|
261
|
+
});
|
|
262
|
+
if (!response.ok) return {
|
|
263
|
+
status: "slack_api_error",
|
|
264
|
+
error: response.statusText
|
|
265
|
+
};
|
|
266
|
+
const data = await response.json();
|
|
267
|
+
if (data.error) switch (data.error) {
|
|
268
|
+
case "app_approval_request_eligible": return { status: "app_approval_request_eligible" };
|
|
269
|
+
case "app_approval_request_pending": return { status: "app_approval_request_pending" };
|
|
270
|
+
case "app_approval_request_denied": return { status: "app_approval_request_denied" };
|
|
271
|
+
case "invalid_app_id":
|
|
272
|
+
if (serviceToken) {
|
|
273
|
+
const { hasAccess } = await verifyServiceTokenAccess({
|
|
274
|
+
serviceToken,
|
|
275
|
+
appId
|
|
276
|
+
});
|
|
277
|
+
if (!hasAccess) return { status: "no_access" };
|
|
278
|
+
}
|
|
279
|
+
return { status: "invalid_app_id" };
|
|
280
|
+
default: return { status: "unknown_error" };
|
|
281
|
+
}
|
|
282
|
+
return {
|
|
283
|
+
status: "installed",
|
|
284
|
+
botToken: data.api_access_tokens?.bot,
|
|
285
|
+
appLevelToken: data.api_access_tokens?.app_level,
|
|
286
|
+
userToken: data.api_access_tokens?.user
|
|
287
|
+
};
|
|
288
|
+
}
|
|
289
|
+
var init_slack = __esmMin((() => {
|
|
290
|
+
init_errors$1();
|
|
291
|
+
init_errors();
|
|
292
|
+
}));
|
|
293
|
+
//#endregion
|
|
294
|
+
//#region src/internal/vercel/index.ts
|
|
295
|
+
async function getProject({ projectId, token, teamId }) {
|
|
296
|
+
const url = new URL(`https://api.vercel.com/v9/projects/${encodeURIComponent(projectId)}`);
|
|
297
|
+
if (teamId) url.searchParams.set("teamId", teamId);
|
|
298
|
+
const response = await fetch(url, {
|
|
299
|
+
method: "GET",
|
|
300
|
+
headers: { Authorization: `Bearer ${token}` }
|
|
301
|
+
});
|
|
302
|
+
if (!response.ok) throw await HTTPError.fromResponse("Failed to access Vercel project", response);
|
|
303
|
+
}
|
|
304
|
+
async function updateProtectionBypass({ projectId, token, teamId }) {
|
|
305
|
+
const newSecret = crypto.randomBytes(16).toString("hex");
|
|
306
|
+
const note = "Created by @vercel/slack-bolt";
|
|
307
|
+
const url = new URL(`https://api.vercel.com/v1/projects/${encodeURIComponent(projectId)}/protection-bypass`);
|
|
308
|
+
if (teamId) url.searchParams.set("teamId", teamId);
|
|
309
|
+
const response = await fetch(url, {
|
|
310
|
+
method: "PATCH",
|
|
311
|
+
headers: {
|
|
312
|
+
Authorization: `Bearer ${token}`,
|
|
313
|
+
"Content-Type": "application/json"
|
|
314
|
+
},
|
|
315
|
+
body: JSON.stringify({ generate: {
|
|
316
|
+
secret: newSecret,
|
|
317
|
+
note
|
|
318
|
+
} })
|
|
319
|
+
});
|
|
320
|
+
if (!response.ok) throw await HTTPError.fromResponse("Failed to update protection bypass", response);
|
|
321
|
+
return newSecret;
|
|
322
|
+
}
|
|
323
|
+
async function addEnvironmentVariables({ projectId, token, teamId, envs, upsert = true }) {
|
|
324
|
+
const url = new URL(`https://api.vercel.com/v10/projects/${encodeURIComponent(projectId)}/env`);
|
|
325
|
+
if (teamId) url.searchParams.set("teamId", teamId);
|
|
326
|
+
if (upsert) url.searchParams.set("upsert", "true");
|
|
327
|
+
const response = await fetch(url, {
|
|
328
|
+
method: "POST",
|
|
329
|
+
headers: {
|
|
330
|
+
Authorization: `Bearer ${token}`,
|
|
331
|
+
"Content-Type": "application/json"
|
|
332
|
+
},
|
|
333
|
+
body: JSON.stringify(envs)
|
|
334
|
+
});
|
|
335
|
+
if (!response.ok) throw await HTTPError.fromResponse("Failed to create environment variables", response);
|
|
336
|
+
return response.json();
|
|
337
|
+
}
|
|
338
|
+
async function cancelDeployment({ deploymentId, token, teamId }) {
|
|
339
|
+
const url = new URL(`https://api.vercel.com/v12/deployments/${encodeURIComponent(deploymentId)}/cancel`);
|
|
340
|
+
if (teamId) url.searchParams.set("teamId", teamId);
|
|
341
|
+
const response = await fetch(url, {
|
|
342
|
+
method: "PATCH",
|
|
343
|
+
headers: { Authorization: `Bearer ${token}` }
|
|
344
|
+
});
|
|
345
|
+
if (!response.ok) throw await HTTPError.fromResponse("Failed to cancel deployment", response);
|
|
346
|
+
}
|
|
347
|
+
async function createDeployment({ deploymentId, projectId, token, teamId }) {
|
|
348
|
+
const url = new URL("https://api.vercel.com/v13/deployments");
|
|
349
|
+
if (teamId) url.searchParams.set("teamId", teamId);
|
|
350
|
+
url.searchParams.set("forceNew", "1");
|
|
351
|
+
const response = await fetch(url, {
|
|
352
|
+
method: "POST",
|
|
353
|
+
headers: {
|
|
354
|
+
Authorization: `Bearer ${token}`,
|
|
355
|
+
"Content-Type": "application/json"
|
|
356
|
+
},
|
|
357
|
+
body: JSON.stringify({
|
|
358
|
+
deploymentId,
|
|
359
|
+
name: projectId,
|
|
360
|
+
project: projectId
|
|
361
|
+
})
|
|
362
|
+
});
|
|
363
|
+
if (!response.ok) throw await HTTPError.fromResponse("Failed to create deployment", response);
|
|
364
|
+
const data = await response.json();
|
|
365
|
+
return {
|
|
366
|
+
id: data.id,
|
|
367
|
+
url: data.url
|
|
368
|
+
};
|
|
369
|
+
}
|
|
370
|
+
async function getActiveBranches({ projectId, token, teamId }) {
|
|
371
|
+
const params = new URLSearchParams({
|
|
372
|
+
active: "1",
|
|
373
|
+
limit: "100"
|
|
374
|
+
});
|
|
375
|
+
if (teamId) params.set("teamId", teamId);
|
|
376
|
+
const response = await fetch(`https://api.vercel.com/v5/projects/${encodeURIComponent(projectId)}/branches?${params}`, { headers: { Authorization: `Bearer ${token}` } });
|
|
377
|
+
if (!response.ok) throw await HTTPError.fromResponse("Failed to fetch active branches", response);
|
|
378
|
+
const data = await response.json();
|
|
379
|
+
return new Set(data.branches?.map((b) => b.branch) ?? []);
|
|
380
|
+
}
|
|
381
|
+
async function getEnvironmentVariables({ projectId, token, teamId }) {
|
|
382
|
+
const url = new URL(`https://api.vercel.com/v9/projects/${encodeURIComponent(projectId)}/env`);
|
|
383
|
+
if (teamId) url.searchParams.set("teamId", teamId);
|
|
384
|
+
const response = await fetch(url, { headers: { Authorization: `Bearer ${token}` } });
|
|
385
|
+
if (!response.ok) throw await HTTPError.fromResponse("Failed to fetch environment variables", response);
|
|
386
|
+
return (await response.json()).envs ?? [];
|
|
387
|
+
}
|
|
388
|
+
async function getEnvironmentVariable({ projectId, envId, token, teamId }) {
|
|
389
|
+
const url = new URL(`https://api.vercel.com/v9/projects/${encodeURIComponent(projectId)}/env/${encodeURIComponent(envId)}`);
|
|
390
|
+
if (teamId) url.searchParams.set("teamId", teamId);
|
|
391
|
+
const response = await fetch(url, { headers: { Authorization: `Bearer ${token}` } });
|
|
392
|
+
if (!response.ok) throw await HTTPError.fromResponse("Failed to fetch environment variable", response);
|
|
393
|
+
return (await response.json()).value ?? null;
|
|
394
|
+
}
|
|
395
|
+
async function deleteEnvironmentVariable({ projectId, envId, token, teamId }) {
|
|
396
|
+
const url = new URL(`https://api.vercel.com/v9/projects/${encodeURIComponent(projectId)}/env/${encodeURIComponent(envId)}`);
|
|
397
|
+
if (teamId) url.searchParams.set("teamId", teamId);
|
|
398
|
+
const response = await fetch(url, {
|
|
399
|
+
method: "DELETE",
|
|
400
|
+
headers: { Authorization: `Bearer ${token}` }
|
|
401
|
+
});
|
|
402
|
+
if (!response.ok) throw await HTTPError.fromResponse("Failed to delete environment variable", response);
|
|
403
|
+
}
|
|
404
|
+
var init_vercel = __esmMin((() => {
|
|
405
|
+
init_errors$1();
|
|
406
|
+
}));
|
|
407
|
+
//#endregion
|
|
408
|
+
//#region src/logger.ts
|
|
409
|
+
function isDebug() {
|
|
410
|
+
return process.env.VERCEL_SLACK_DEBUG === "1" || process.env.VERCEL_SLACK_DEBUG === "true";
|
|
411
|
+
}
|
|
412
|
+
function isSensitiveKey(key) {
|
|
413
|
+
const lower = key.toLowerCase();
|
|
414
|
+
if (SENSITIVE_BODY_KEYS.has(lower)) return true;
|
|
415
|
+
return SENSITIVE_KEY_PATTERNS.some((pattern) => lower.includes(pattern));
|
|
416
|
+
}
|
|
417
|
+
function redactValue(value) {
|
|
418
|
+
if (value.length <= 4) return "****";
|
|
419
|
+
return `****${value.slice(-4)}`;
|
|
420
|
+
}
|
|
421
|
+
function redactSensitiveFields(obj) {
|
|
422
|
+
if (Array.isArray(obj)) return obj.map((item) => redactSensitiveFields(item));
|
|
423
|
+
if (obj !== null && typeof obj === "object") {
|
|
424
|
+
const result = {};
|
|
425
|
+
for (const [key, val] of Object.entries(obj)) if (isSensitiveKey(key) && typeof val === "string") result[key] = redactValue(val);
|
|
426
|
+
else result[key] = redactSensitiveFields(val);
|
|
427
|
+
return result;
|
|
428
|
+
}
|
|
429
|
+
return obj;
|
|
430
|
+
}
|
|
431
|
+
function redactBody(body) {
|
|
432
|
+
try {
|
|
433
|
+
const redacted = redactSensitiveFields(JSON.parse(body));
|
|
434
|
+
return JSON.stringify(redacted);
|
|
435
|
+
} catch {
|
|
436
|
+
return `<non-JSON body, ${body.length} bytes>`;
|
|
437
|
+
}
|
|
438
|
+
}
|
|
439
|
+
function formatHeaders(headers) {
|
|
440
|
+
const redacted = (headers instanceof Headers ? [...headers.entries()] : Object.entries(headers ?? {})).map(([k, v]) => REDACTED_HEADERS.has(k.toLowerCase()) ? [k, `****${v.slice(-4)}`] : [k, v]);
|
|
441
|
+
return JSON.stringify(Object.fromEntries(redacted));
|
|
442
|
+
}
|
|
443
|
+
function enableFetchDebugLogging() {
|
|
444
|
+
if (globalThis.fetch.__debugPatched) return;
|
|
445
|
+
const originalFetch = globalThis.fetch;
|
|
446
|
+
const patched = async (input, init) => {
|
|
447
|
+
const method = init?.method ?? "GET";
|
|
448
|
+
const url = input instanceof Request ? input.url : input.toString();
|
|
449
|
+
const start = performance.now();
|
|
450
|
+
log.debug(`-> ${method} ${url}`);
|
|
451
|
+
log.debug(` headers: ${formatHeaders(init?.headers)}`);
|
|
452
|
+
if (typeof init?.body === "string") log.debug(` body: ${redactBody(init.body)}`);
|
|
453
|
+
const response = await originalFetch(input, init);
|
|
454
|
+
const ms = (performance.now() - start).toFixed(0);
|
|
455
|
+
log.debug(`<- ${response.status} ${response.statusText} (${ms}ms)`);
|
|
456
|
+
log.debug(` headers: ${formatHeaders(response.headers)}`);
|
|
457
|
+
const clone = response.clone();
|
|
458
|
+
try {
|
|
459
|
+
const body = await clone.text();
|
|
460
|
+
if (body) log.debug(` body: ${redactBody(body)}`);
|
|
461
|
+
} catch {}
|
|
462
|
+
return response;
|
|
463
|
+
};
|
|
464
|
+
patched.__debugPatched = true;
|
|
465
|
+
globalThis.fetch = patched;
|
|
466
|
+
}
|
|
467
|
+
var BOLD, RESET, DIM, GREEN, YELLOW, log, REDACTED_HEADERS, SENSITIVE_BODY_KEYS, SENSITIVE_KEY_PATTERNS, startMessage;
|
|
468
|
+
var init_logger = __esmMin((() => {
|
|
469
|
+
BOLD = "\x1B[1m";
|
|
470
|
+
RESET = "\x1B[0m";
|
|
471
|
+
DIM = "\x1B[2m";
|
|
472
|
+
GREEN = "\x1B[32m";
|
|
473
|
+
YELLOW = "\x1B[33m";
|
|
474
|
+
log = {
|
|
475
|
+
step: (msg) => console.log(` ${msg} ...`),
|
|
476
|
+
success: (msg) => console.log(`${GREEN}✓${RESET} ${msg}`),
|
|
477
|
+
info: (msg) => console.log(` ${msg}`),
|
|
478
|
+
warning: (msg) => console.log(`${YELLOW}⚠${RESET} ${msg}`),
|
|
479
|
+
error: (...args) => console.error(...args),
|
|
480
|
+
debug: (...args) => {
|
|
481
|
+
if (isDebug()) {
|
|
482
|
+
const msg = args.map((a) => typeof a === "string" ? a : String(a)).join(" ");
|
|
483
|
+
console.debug(`${DIM}${msg}${RESET}`);
|
|
484
|
+
}
|
|
485
|
+
}
|
|
486
|
+
};
|
|
487
|
+
REDACTED_HEADERS = new Set(["authorization"]);
|
|
488
|
+
SENSITIVE_BODY_KEYS = new Set([
|
|
489
|
+
"token",
|
|
490
|
+
"bot",
|
|
491
|
+
"app_level",
|
|
492
|
+
"user",
|
|
493
|
+
"secret",
|
|
494
|
+
"client_secret",
|
|
495
|
+
"signing_secret",
|
|
496
|
+
"verification_token",
|
|
497
|
+
"bot_token",
|
|
498
|
+
"user_token",
|
|
499
|
+
"app_level_token",
|
|
500
|
+
"value",
|
|
501
|
+
"access_token",
|
|
502
|
+
"refresh_token",
|
|
503
|
+
"password"
|
|
504
|
+
]);
|
|
505
|
+
SENSITIVE_KEY_PATTERNS = [
|
|
506
|
+
"token",
|
|
507
|
+
"secret",
|
|
508
|
+
"password",
|
|
509
|
+
"credential"
|
|
510
|
+
];
|
|
511
|
+
startMessage = (version, branch, commit, appId) => {
|
|
512
|
+
const lines = [`▲ @vercel/slack-bolt ${version ?? ""}`];
|
|
513
|
+
if (branch) lines.push(` - Branch: ${branch}`);
|
|
514
|
+
if (commit) lines.push(` - Commit: ${commit.slice(0, 7)}`);
|
|
515
|
+
if (appId) lines.push(` - App ID: ${appId}`);
|
|
516
|
+
return `${BOLD}${lines.join("\n")}${RESET}\n`;
|
|
517
|
+
};
|
|
518
|
+
}));
|
|
519
|
+
//#endregion
|
|
520
|
+
//#region src/preview.ts
|
|
521
|
+
var preview;
|
|
522
|
+
var init_preview = __esmMin((() => {
|
|
523
|
+
init_manifest();
|
|
524
|
+
init_parse();
|
|
525
|
+
init_slack();
|
|
526
|
+
init_vercel();
|
|
527
|
+
init_logger();
|
|
528
|
+
preview = async (params, context) => {
|
|
529
|
+
const { branch, projectId, deploymentUrl, teamId, commitAuthor, commitMessage: commitMsg, commitSha, slackAppId, slackConfigurationToken, slackServiceToken, manifestPath, vercelApiToken } = params;
|
|
530
|
+
const cli = context === "cli";
|
|
531
|
+
const branchUrl = params.branchUrl ?? deploymentUrl;
|
|
532
|
+
let bypassSecret = params.automationBypassSecret;
|
|
533
|
+
if (!bypassSecret) {
|
|
534
|
+
if (cli) log.step("Generating automation bypass secret");
|
|
535
|
+
bypassSecret = await updateProtectionBypass({
|
|
536
|
+
projectId,
|
|
537
|
+
token: vercelApiToken,
|
|
538
|
+
teamId
|
|
539
|
+
});
|
|
540
|
+
if (cli) log.success("Automation bypass secret generated");
|
|
541
|
+
}
|
|
542
|
+
if (cli) log.step(`Reading manifest from ${manifestPath}`);
|
|
543
|
+
const manifest = parseManifest(fs.readFileSync(path.join(process.cwd(), manifestPath), "utf8"), manifestPath);
|
|
544
|
+
const newManifest = createNewManifest({
|
|
545
|
+
originalManifest: manifest,
|
|
546
|
+
branchUrl,
|
|
547
|
+
bypassSecret,
|
|
548
|
+
branch,
|
|
549
|
+
commitSha,
|
|
550
|
+
commitMessage: commitMsg,
|
|
551
|
+
commitAuthor
|
|
552
|
+
});
|
|
553
|
+
fs.writeFileSync(path.join(process.cwd(), manifestPath), stringifyManifest(newManifest, manifestPath), "utf8");
|
|
554
|
+
if (cli) log.success(`Manifest updated for ${branchUrl}`);
|
|
555
|
+
if (cli) log.step("Creating or updating Slack app");
|
|
556
|
+
const { isNew, app } = await upsertSlackApp({
|
|
557
|
+
token: slackConfigurationToken,
|
|
558
|
+
appId: slackAppId,
|
|
559
|
+
manifest: newManifest
|
|
560
|
+
});
|
|
561
|
+
if (isNew) {
|
|
562
|
+
const credentialEnvs = [];
|
|
563
|
+
if (app.app_id) credentialEnvs.push({
|
|
564
|
+
key: "SLACK_APP_ID",
|
|
565
|
+
value: app.app_id,
|
|
566
|
+
type: "encrypted",
|
|
567
|
+
target: ["preview"],
|
|
568
|
+
gitBranch: branch,
|
|
569
|
+
comment: `Created by @vercel/slack-bolt for app ${app.app_id} on branch ${branch}`
|
|
570
|
+
});
|
|
571
|
+
if (app.credentials?.client_id) credentialEnvs.push({
|
|
572
|
+
key: "SLACK_CLIENT_ID",
|
|
573
|
+
value: app.credentials.client_id,
|
|
574
|
+
type: "encrypted",
|
|
575
|
+
target: ["preview"],
|
|
576
|
+
gitBranch: branch,
|
|
577
|
+
comment: `Created by @vercel/slack-bolt for app ${app.app_id} on branch ${branch}`
|
|
578
|
+
});
|
|
579
|
+
if (app.credentials?.client_secret) credentialEnvs.push({
|
|
580
|
+
key: "SLACK_CLIENT_SECRET",
|
|
581
|
+
value: app.credentials.client_secret,
|
|
582
|
+
type: "encrypted",
|
|
583
|
+
target: ["preview"],
|
|
584
|
+
gitBranch: branch,
|
|
585
|
+
comment: `Created by @vercel/slack-bolt for app ${app.app_id} on branch ${branch}`
|
|
586
|
+
});
|
|
587
|
+
if (app.credentials?.signing_secret) credentialEnvs.push({
|
|
588
|
+
key: "SLACK_SIGNING_SECRET",
|
|
589
|
+
value: app.credentials.signing_secret,
|
|
590
|
+
type: "encrypted",
|
|
591
|
+
target: ["preview"],
|
|
592
|
+
gitBranch: branch,
|
|
593
|
+
comment: `Created by @vercel/slack-bolt for app ${app.app_id} on branch ${branch}`
|
|
594
|
+
});
|
|
595
|
+
if (credentialEnvs.length > 0) await addEnvironmentVariables({
|
|
596
|
+
projectId,
|
|
597
|
+
token: vercelApiToken,
|
|
598
|
+
teamId,
|
|
599
|
+
envs: credentialEnvs
|
|
600
|
+
});
|
|
601
|
+
}
|
|
602
|
+
if (cli) log.success(`${isNew ? "Created" : "Updated"} Slack app ${app.app_id}`);
|
|
603
|
+
if (cli) log.step("Installing Slack app");
|
|
604
|
+
const { status, botToken, appLevelToken, userToken } = await installApp({
|
|
605
|
+
serviceToken: slackServiceToken,
|
|
606
|
+
appId: app.app_id,
|
|
607
|
+
botScopes: manifest.oauth_config?.scopes?.bot ?? []
|
|
608
|
+
});
|
|
609
|
+
if (isNew) {
|
|
610
|
+
const tokenEnvs = [];
|
|
611
|
+
if (botToken) tokenEnvs.push({
|
|
612
|
+
key: "SLACK_BOT_TOKEN",
|
|
613
|
+
value: botToken,
|
|
614
|
+
type: "encrypted",
|
|
615
|
+
target: ["preview"],
|
|
616
|
+
gitBranch: branch,
|
|
617
|
+
comment: `Created by @vercel/slack-bolt for app ${app.app_id} on branch ${branch}`
|
|
618
|
+
});
|
|
619
|
+
if (appLevelToken) tokenEnvs.push({
|
|
620
|
+
key: "SLACK_APP_LEVEL_TOKEN",
|
|
621
|
+
value: appLevelToken,
|
|
622
|
+
type: "encrypted",
|
|
623
|
+
target: ["preview"],
|
|
624
|
+
gitBranch: branch,
|
|
625
|
+
comment: `Created by @vercel/slack-bolt for app ${app.app_id} on branch ${branch}`
|
|
626
|
+
});
|
|
627
|
+
if (userToken) tokenEnvs.push({
|
|
628
|
+
key: "SLACK_USER_TOKEN",
|
|
629
|
+
value: userToken,
|
|
630
|
+
type: "encrypted",
|
|
631
|
+
target: ["preview"],
|
|
632
|
+
gitBranch: branch,
|
|
633
|
+
comment: `Created by @vercel/slack-bolt for app ${app.app_id} on branch ${branch}`
|
|
634
|
+
});
|
|
635
|
+
if (tokenEnvs.length > 0) await addEnvironmentVariables({
|
|
636
|
+
projectId,
|
|
637
|
+
token: vercelApiToken,
|
|
638
|
+
teamId,
|
|
639
|
+
envs: tokenEnvs
|
|
640
|
+
});
|
|
641
|
+
}
|
|
642
|
+
if (cli) {
|
|
643
|
+
switch (status) {
|
|
644
|
+
case "missing_service_token":
|
|
645
|
+
log.warning("SLACK_SERVICE_TOKEN is not set — app must be installed manually");
|
|
646
|
+
log.info("https://docs.slack.dev/authentication/tokens/#service");
|
|
647
|
+
break;
|
|
648
|
+
case "installed":
|
|
649
|
+
log.success(`Installed Slack app ${app.app_id}`);
|
|
650
|
+
break;
|
|
651
|
+
case "app_approval_request_eligible":
|
|
652
|
+
log.warning("App requires approval before it can be installed");
|
|
653
|
+
break;
|
|
654
|
+
case "app_approval_request_pending":
|
|
655
|
+
log.warning("App is pending approval before it can be installed");
|
|
656
|
+
break;
|
|
657
|
+
case "app_approval_request_denied":
|
|
658
|
+
log.warning("App approval request was denied");
|
|
659
|
+
break;
|
|
660
|
+
case "no_access":
|
|
661
|
+
log.warning(`SLACK_SERVICE_TOKEN does not have access to app ${app.app_id}. This usually means the service token and configuration token were created by different users. Ensure both tokens are generated by the same Slack user.
|
|
662
|
+
https://docs.slack.dev/authentication/tokens/#service`);
|
|
663
|
+
break;
|
|
664
|
+
case "invalid_app_id":
|
|
665
|
+
log.warning("Invalid app ID. This is a bug in the @vercel/slack-bolt package. Please open an issue at https://github.com/vercel/slack-bolt/issues");
|
|
666
|
+
break;
|
|
667
|
+
case "slack_api_error":
|
|
668
|
+
log.warning("Slack API error while installing the app");
|
|
669
|
+
break;
|
|
670
|
+
case "unknown_error":
|
|
671
|
+
log.warning("Unknown error while installing the app");
|
|
672
|
+
break;
|
|
673
|
+
}
|
|
674
|
+
console.log();
|
|
675
|
+
if (app.app_id) log.info(`View app: https://api.slack.com/apps/${app.app_id}`);
|
|
676
|
+
if (isNew && app.oauth_authorize_url) log.info(`Install URL: ${app.oauth_authorize_url}`);
|
|
677
|
+
console.log();
|
|
678
|
+
}
|
|
679
|
+
return {
|
|
680
|
+
isNew,
|
|
681
|
+
installStatus: status,
|
|
682
|
+
app
|
|
683
|
+
};
|
|
684
|
+
};
|
|
685
|
+
}));
|
|
686
|
+
//#endregion
|
|
687
|
+
export { deleteSlackApp as _, log as a, __commonJSMin as b, cancelDeployment as c, getActiveBranches as d, getEnvironmentVariable as f, authTest as g, init_vercel as h, init_logger as i, createDeployment as l, getProject as m, preview as n, startMessage as o, getEnvironmentVariables as p, enableFetchDebugLogging as r, addEnvironmentVariables as s, init_preview as t, deleteEnvironmentVariable as u, init_slack as v, __esmMin as x, rotateConfigToken as y };
|
|
688
|
+
|
|
689
|
+
//# sourceMappingURL=preview-DAWKwXt1.mjs.map
|