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}