Add a `get-release-notes-since` script (#18445)

Joseph T. Lyons created

Release Notes:

- N/A

Change summary

script/get-pull-requests-since |   4 
script/get-release-notes-since | 125 ++++++++++++++++++++++++++++++++++++
2 files changed, 127 insertions(+), 2 deletions(-)

Detailed changes

script/get-changes-since → script/get-pull-requests-since 🔗

@@ -22,12 +22,12 @@ async function main() {
   const startDate = new Date(process.argv[2]);
   const today = new Date();
 
-  console.log(`Changes from ${startDate} to ${today}\n`);
+  console.log(`Pull requests from ${startDate} to ${today}\n`);
 
   let pullRequestNumbers = getPullRequestNumbers(startDate, today);
 
   // Fetch the pull requests from the GitHub API.
-  console.log("Merged Pull requests:");
+  console.log("Merged pull requests:");
   for (const pullRequestNumber of pullRequestNumbers) {
     const webURL = `https://github.com/zed-industries/zed/pull/${pullRequestNumber}`;
     const apiURL = `https://api.github.com/repos/zed-industries/zed/pulls/${pullRequestNumber}`;

script/get-release-notes-since 🔗

@@ -0,0 +1,125 @@
+#!/usr/bin/env node --redirect-warnings=/dev/null
+
+const { execFileSync } = require("child_process");
+const { GITHUB_ACCESS_TOKEN } = process.env;
+
+main();
+
+async function main() {
+  const startDate = new Date(process.argv[2]);
+  const today = new Date();
+
+  console.log(`Release notes from ${startDate} to ${today}\n`);
+
+  const releases = await getReleases(startDate, today);
+  const previewReleases = releases.filter((release) =>
+    release.tagName.includes("-pre"),
+  );
+
+  const stableReleases = releases.filter(
+    (release) => !release.tagName.includes("-pre"),
+  );
+
+  // Filter out all preview release, as all of those changes have made it to the stable release, except for the latest preview release
+  const aggregatedReleases = stableReleases
+    .concat(previewReleases[0])
+    .reverse();
+
+  const aggregatedReleaseTitles = aggregatedReleases
+    .map((release) => release.name)
+    .join(", ");
+
+  console.log();
+  console.log(`Release titles: ${aggregatedReleaseTitles}`);
+
+  console.log("Release notes:");
+  console.log();
+
+  for (const release of aggregatedReleases) {
+    const publishedDate = release.publishedAt.split("T")[0];
+    console.log(`${release.name}: ${publishedDate}`);
+    console.log();
+    console.log(release.description);
+    console.log();
+  }
+}
+
+async function getReleases(startDate, endDate) {
+  const query = `
+    query ($owner: String!, $repo: String!, $cursor: String) {
+      repository(owner: $owner, name: $repo) {
+        releases(first: 100, orderBy: {field: CREATED_AT, direction: DESC}, after: $cursor) {
+          nodes {
+            tagName
+            name
+            createdAt
+            publishedAt
+            description
+            url
+            author {
+              login
+            }
+          }
+          pageInfo {
+            hasNextPage
+            endCursor
+          }
+        }
+      }
+    }
+  `;
+
+  let allReleases = [];
+  let hasNextPage = true;
+  let cursor = null;
+
+  while (hasNextPage) {
+    const response = await fetch("https://api.github.com/graphql", {
+      method: "POST",
+      headers: {
+        Authorization: `Bearer ${GITHUB_ACCESS_TOKEN}`,
+        "Content-Type": "application/json",
+      },
+      body: JSON.stringify({
+        query,
+        variables: { owner: "zed-industries", repo: "zed", cursor },
+      }),
+    });
+
+    if (!response.ok) {
+      throw new Error(`HTTP error! status: ${response.status}`);
+    }
+
+    const data = await response.json();
+
+    if (data.errors) {
+      throw new Error(`GraphQL error: ${JSON.stringify(data.errors)}`);
+    }
+
+    if (!data.data || !data.data.repository || !data.data.repository.releases) {
+      throw new Error(`Unexpected response structure: ${JSON.stringify(data)}`);
+    }
+
+    const releases = data.data.repository.releases.nodes;
+    allReleases = allReleases.concat(releases);
+
+    hasNextPage = data.data.repository.releases.pageInfo.hasNextPage;
+    cursor = data.data.repository.releases.pageInfo.endCursor;
+
+    lastReleaseOnPage = releases[releases.length - 1];
+
+    if (
+      releases.length > 0 &&
+      new Date(lastReleaseOnPage.createdAt) < startDate
+    ) {
+      break;
+    }
+  }
+
+  const filteredReleases = allReleases.filter((release) => {
+    const releaseDate = new Date(release.createdAt);
+    return releaseDate >= startDate && releaseDate <= endDate;
+  });
+
+  return filteredReleases;
+}