main.js

 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;
10
11async function main() {
12  const octokit = new Octokit({ auth: process.env["GITHUB_TOKEN"] });
13
14  if (!process.env["SLACK_ISSUE_RESPONSE_WEBHOOK_URL"]) {
15    throw new Error("SLACK_ISSUE_RESPONSE_WEBHOOK_URL is not set");
16  }
17
18  const webhook = new IncomingWebhook(
19    process.env["SLACK_ISSUE_RESPONSE_WEBHOOK_URL"],
20  );
21
22  const owner = "zed-industries";
23  const repo = "zed";
24  const staff = await octokit.paginate(octokit.rest.orgs.listMembers, {
25    org: owner,
26    per_page: 100,
27  });
28  let staffHandles = staff.map((member) => member.login);
29  let commenterFilters = staffHandles.map((name) => `-commenter:${name}`);
30  let authorFilters = staffHandles.map((name) => `-author:${name}`);
31
32  const q = [
33    `repo:${owner}/${repo}`,
34    "is:issue",
35    "state:open",
36    "created:>=2025-02-01",
37    "sort:created-asc",
38    ...commenterFilters,
39    ...authorFilters,
40  ];
41
42  const response = await octokit.rest.search.issuesAndPullRequests({
43    q: q.join("+"),
44    per_page: 100,
45  });
46
47  let issues = response.data.items;
48  let issueLines = issues.map((issue, index) => {
49    const formattedDate = new Date(issue.created_at).toLocaleDateString(
50      "en-US",
51      {
52        year: "numeric",
53        month: "short",
54        day: "numeric",
55      },
56    );
57    const sanitizedTitle = issue.title
58      .replaceAll("&", "&")
59      .replaceAll("<", "&lt;")
60      .replaceAll(">", "&gt;");
61
62    return `${index + 1}. ${formattedDate}: <${issue.html_url}|${sanitizedTitle}>\n`;
63  });
64
65  const sections = [];
66  /** @type {string[]} */
67  let currentSection = [];
68  let currentSectionLength = 0;
69
70  for (const issueLine of issueLines) {
71    if (currentSectionLength + issueLine.length <= SECTION_BLOCK_TEXT_LIMIT) {
72      currentSection.push(issueLine);
73      currentSectionLength += issueLine.length;
74    } else {
75      sections.push(currentSection);
76      currentSection = [];
77      currentSectionLength = 0;
78    }
79  }
80
81  if (currentSection.length > 0) {
82    sections.push(currentSection);
83  }
84
85  const blocks = sections.map((section) => ({
86    type: "section",
87    text: {
88      type: "mrkdwn",
89      text: section.join("").trimEnd(),
90    },
91  }));
92
93  await webhook.send({ blocks });
94}
95
96main().catch((error) => {
97  console.error("An error occurred:", error);
98  process.exit(1);
99});