1import { Octokit } from "@octokit/rest";
2import { IncomingWebhook } from "@slack/webhook";
3
4/**
5 * The maximum length of the `text` in a section block.
6 *
7 * [Slack Docs](https://api.slack.com/reference/block-kit/blocks#section)
8 */
9const SECTION_BLOCK_TEXT_LIMIT = 3000;
10const GITHUB_ISSUES_URL = "https://github.com/zed-industries/zed/issues";
11
12async function main() {
13 const octokit = new Octokit({
14 auth: process.env["ISSUE_RESPONSE_GITHUB_TOKEN"],
15 });
16
17 if (!process.env["SLACK_ISSUE_RESPONSE_WEBHOOK_URL"]) {
18 throw new Error("SLACK_ISSUE_RESPONSE_WEBHOOK_URL is not set");
19 }
20
21 const webhook = new IncomingWebhook(
22 process.env["SLACK_ISSUE_RESPONSE_WEBHOOK_URL"],
23 );
24
25 const owner = "zed-industries";
26 const repo = "zed";
27 const staff = await octokit.paginate(octokit.rest.teams.listMembersInOrg, {
28 org: owner,
29 team_slug: "staff",
30 per_page: 100,
31 });
32 let staffHandles = staff.map((member) => member.login);
33 let commenterFilters = staffHandles.map((name) => `-commenter:${name}`);
34 let authorFilters = staffHandles.map((name) => `-author:${name}`);
35
36 const q = [
37 `repo:${owner}/${repo}`,
38 "is:issue",
39 "state:open",
40 "created:>=2025-02-01",
41 "sort:created-asc",
42 ...commenterFilters,
43 ...authorFilters,
44 ];
45
46 const response = await octokit.rest.search.issuesAndPullRequests({
47 q: q.join("+"),
48 per_page: 100,
49 });
50
51 let issues = response.data.items;
52 let issueLines = issues.map((issue, index) => {
53 const formattedDate = new Date(issue.created_at).toLocaleDateString(
54 "en-US",
55 {
56 year: "numeric",
57 month: "short",
58 day: "numeric",
59 },
60 );
61 const sanitizedTitle = issue.title
62 .replaceAll("&", "&")
63 .replaceAll("<", "<")
64 .replaceAll(">", ">");
65
66 return `${index + 1}. ${formattedDate}: <${issue.html_url}|${sanitizedTitle}>\n`;
67 });
68
69 const sections = [];
70 /** @type {string[]} */
71 let currentSection = [];
72 let currentSectionLength = 0;
73
74 for (const issueLine of issueLines) {
75 if (currentSectionLength + issueLine.length <= SECTION_BLOCK_TEXT_LIMIT) {
76 currentSection.push(issueLine);
77 currentSectionLength += issueLine.length;
78 } else {
79 sections.push(currentSection);
80 currentSection = [];
81 currentSectionLength = 0;
82 }
83 }
84
85 if (currentSection.length > 0) {
86 sections.push(currentSection);
87 }
88
89 const blocks = sections.map((section) => ({
90 type: "section",
91 text: {
92 type: "mrkdwn",
93 text: section.join("").trimEnd(),
94 },
95 }));
96
97 const issuesUrl = `${GITHUB_ISSUES_URL}?q=${encodeURIComponent(q.join(" "))}`;
98
99 blocks.push({
100 type: "section",
101 text: {
102 type: "mrkdwn",
103 text: `<${issuesUrl}|View on GitHub>`,
104 },
105 });
106
107 await webhook.send({ blocks });
108}
109
110main().catch((error) => {
111 console.error("An error occurred:", error);
112 process.exit(1);
113});