@@ -205,6 +205,7 @@ jobs:
echo "invalid release tag ${GITHUB_REF_NAME}. expected ${expected_tag_name}"
exit 1
fi
+ script/draft-release-notes "$version" "$channel" > target/release-notes.md
- name: Generate license file
run: script/generate-licenses
@@ -248,7 +249,7 @@ jobs:
target/aarch64-apple-darwin/release/Zed-aarch64.dmg
target/x86_64-apple-darwin/release/Zed-x86_64.dmg
target/release/Zed.dmg
- body: ""
+ body_file: target/release-notes.md
env:
GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
@@ -0,0 +1,109 @@
+#!/usr/bin/env node --redirect-warnings=/dev/null
+
+const { execFileSync } = require("child_process");
+
+main();
+
+async function main() {
+ let version = process.argv[2];
+ let channel = process.argv[3];
+ let parts = version.split(".");
+
+ if (
+ process.argv.length != 4 ||
+ parts.length != 3 ||
+ parts.find((part) => isNaN(part)) != null ||
+ (channel != "stable" && channel != "preview")
+ ) {
+ console.log("Usage: draft-release-notes <version> {stable|preview}");
+ process.exit(1);
+ }
+
+ let priorVersion = [parts[0], parts[1], parts[2] - 1].join(".");
+ let suffix = "";
+
+ if (channel == "preview") {
+ suffix = "-pre";
+ if (parts[2] == 0) {
+ priorVersion = [parts[0], parts[1] - 1, 0].join(".");
+ }
+ } else if (!tagExists("v${priorVersion}")) {
+ console.log("Copy the release notes from preview.");
+ process.exit(0);
+ }
+
+ let [tag, priorTag] = [`v${version}${suffix}`, `v${priorVersion}${suffix}`];
+
+ const newCommits = getCommits(priorTag, tag);
+
+ let releaseNotes = [];
+ let missing = [];
+ let skipped = [];
+
+ for (const commit of newCommits) {
+ let link = "https://github.com/zed-industries/zed/pull/" + commit.pr;
+ let notes = commit.releaseNotes;
+ if (commit.pr == "") {
+ link = "https://github.com/zed-industries/zed/commits/" + commit.hash;
+ } else if (!notes.includes("zed-industries/zed/issues")) {
+ notes = notes + " ([#" + commit.pr + "](" + link + "))";
+ }
+
+ if (commit.releaseNotes == "") {
+ missing.push("- MISSING " + commit.firstLine + " " + link);
+ } else if (commit.releaseNotes.startsWith("- N/A")) {
+ skipped.push("- N/A " + commit.firstLine + " " + link);
+ } else {
+ releaseNotes.push(notes);
+ }
+ }
+
+ console.log(releaseNotes.join("\n") + "\n");
+ console.log("<!-- ");
+ console.log(missing.join("\n"));
+ console.log(skipped.join("\n"));
+ console.log("-->");
+}
+
+function getCommits(oldTag, newTag) {
+ const pullRequestNumbers = execFileSync(
+ "git",
+ ["log", `${oldTag}..${newTag}`, "--format=DIVIDER\n%H|||%B"],
+ { encoding: "utf8" },
+ )
+ .replace(/\r\n/g, "\n")
+ .split("DIVIDER\n")
+ .filter((commit) => commit.length > 0)
+ .map((commit) => {
+ let [hash, firstLine] = commit.split("\n")[0].split("|||");
+ let cherryPick = firstLine.match(/\(cherry-pick #([0-9]+)\)/)?.[1] || "";
+ let pr = firstLine.match(/\(#(\d+)\)$/)?.[1] || "";
+ let releaseNotes = (commit.split(/Release notes:.*\n/i)[1] || "")
+ .split("\n\n")[0]
+ .trim()
+ .replace(/\n(?![\n-])/g, " ");
+
+ if (releaseNotes.includes("<public_issue_number_if_exists>")) {
+ releaseNotes = "";
+ }
+
+ return {
+ hash,
+ pr,
+ cherryPick,
+ releaseNotes,
+ firstLine,
+ };
+ });
+
+ return pullRequestNumbers;
+}
+
+function tagExists(tag) {
+ try {
+ execFileSync("git", ["rev-parse", "--verify", tag]);
+ return true;
+ } catch (e) {
+ return false;
+ }
+}