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