1#!/usr/bin/env node --redirect-warnings=/dev/null
  2
  3const { execFileSync } = require("child_process");
  4const { GITHUB_ACCESS_TOKEN } = process.env;
  5
  6main();
  7
  8async function main() {
  9  const startDate = new Date(process.argv[2]);
 10  const today = new Date();
 11
 12  console.log(`Release notes from ${startDate} to ${today}\n`);
 13
 14  const releases = await getReleases(startDate, today);
 15  const previewReleases = releases.filter((release) =>
 16    release.tagName.includes("-pre"),
 17  );
 18
 19  const stableReleases = releases.filter(
 20    (release) => !release.tagName.includes("-pre"),
 21  );
 22
 23  // Filter out all preview release, as all of those changes have made it to the stable release, except for the latest preview release
 24  const aggregatedReleases = stableReleases
 25    .concat(previewReleases[0])
 26    .reverse();
 27
 28  const aggregatedReleaseTitles = aggregatedReleases
 29    .map((release) => release.name)
 30    .join(", ");
 31
 32  console.log();
 33  console.log(`Release titles: ${aggregatedReleaseTitles}`);
 34
 35  console.log("Release notes:");
 36  console.log();
 37
 38  for (const release of aggregatedReleases) {
 39    const publishedDate = release.publishedAt.split("T")[0];
 40    console.log(`${release.name}: ${publishedDate}`);
 41    console.log();
 42    console.log(release.description);
 43    console.log();
 44  }
 45}
 46
 47async function getReleases(startDate, endDate) {
 48  const query = `
 49    query ($owner: String!, $repo: String!, $cursor: String) {
 50      repository(owner: $owner, name: $repo) {
 51        releases(first: 100, orderBy: {field: CREATED_AT, direction: DESC}, after: $cursor) {
 52          nodes {
 53            tagName
 54            name
 55            createdAt
 56            publishedAt
 57            description
 58            url
 59            author {
 60              login
 61            }
 62          }
 63          pageInfo {
 64            hasNextPage
 65            endCursor
 66          }
 67        }
 68      }
 69    }
 70  `;
 71
 72  let allReleases = [];
 73  let hasNextPage = true;
 74  let cursor = null;
 75
 76  while (hasNextPage) {
 77    const response = await fetch("https://api.github.com/graphql", {
 78      method: "POST",
 79      headers: {
 80        Authorization: `Bearer ${GITHUB_ACCESS_TOKEN}`,
 81        "Content-Type": "application/json",
 82      },
 83      body: JSON.stringify({
 84        query,
 85        variables: { owner: "zed-industries", repo: "zed", cursor },
 86      }),
 87    });
 88
 89    if (!response.ok) {
 90      throw new Error(`HTTP error! status: ${response.status}`);
 91    }
 92
 93    const data = await response.json();
 94
 95    if (data.errors) {
 96      throw new Error(`GraphQL error: ${JSON.stringify(data.errors)}`);
 97    }
 98
 99    if (!data.data || !data.data.repository || !data.data.repository.releases) {
100      throw new Error(`Unexpected response structure: ${JSON.stringify(data)}`);
101    }
102
103    const releases = data.data.repository.releases.nodes;
104    allReleases = allReleases.concat(releases);
105
106    hasNextPage = data.data.repository.releases.pageInfo.hasNextPage;
107    cursor = data.data.repository.releases.pageInfo.endCursor;
108
109    lastReleaseOnPage = releases[releases.length - 1];
110
111    if (
112      releases.length > 0 &&
113      new Date(lastReleaseOnPage.createdAt) < startDate
114    ) {
115      break;
116    }
117  }
118
119  const filteredReleases = allReleases.filter((release) => {
120    const releaseDate = new Date(release.createdAt);
121    return releaseDate >= startDate && releaseDate <= endDate;
122  });
123
124  return filteredReleases;
125}