#!/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; }