1#!/usr/bin/env node --redirect-warnings=/dev/null
2
3const { execFileSync } = require("child_process");
4
5main();
6
7async function main() {
8 let version = process.argv[2];
9 let channel = process.argv[3];
10 let parts = version.split(".");
11
12 if (
13 process.argv.length != 4 ||
14 parts.length != 3 ||
15 parts.find((part) => isNaN(part)) != null ||
16 (channel != "stable" && channel != "preview")
17 ) {
18 console.log("Usage: draft-release-notes <version> {stable|preview}");
19 process.exit(1);
20 }
21
22 let priorVersion = [parts[0], parts[1], parts[2] - 1].join(".");
23 let suffix = "";
24
25 if (channel == "preview") {
26 suffix = "-pre";
27 if (parts[2] == 0) {
28 priorVersion = [parts[0], parts[1] - 1, 0].join(".");
29 }
30 } else if (!tagExists("v${priorVersion}")) {
31 console.log("Copy the release notes from preview.");
32 process.exit(0);
33 }
34
35 let [tag, priorTag] = [`v${version}${suffix}`, `v${priorVersion}${suffix}`];
36
37 const newCommits = getCommits(priorTag, tag);
38
39 let releaseNotes = [];
40 let missing = [];
41 let skipped = [];
42
43 for (const commit of newCommits) {
44 let link = "https://github.com/zed-industries/zed/pull/" + commit.pr;
45 let notes = commit.releaseNotes;
46 if (commit.pr == "") {
47 link = "https://github.com/zed-industries/zed/commits/" + commit.hash;
48 } else if (!notes.includes("zed-industries/zed/issues")) {
49 notes = notes + " ([#" + commit.pr + "](" + link + "))";
50 }
51
52 if (commit.releaseNotes == "") {
53 missing.push("- MISSING " + commit.firstLine + " " + link);
54 } else if (commit.releaseNotes.startsWith("- N/A")) {
55 skipped.push("- N/A " + commit.firstLine + " " + link);
56 } else {
57 releaseNotes.push(notes);
58 }
59 }
60
61 console.log(releaseNotes.join("\n") + "\n");
62 console.log("<!-- ");
63 console.log(missing.join("\n"));
64 console.log(skipped.join("\n"));
65 console.log("-->");
66}
67
68function getCommits(oldTag, newTag) {
69 const pullRequestNumbers = execFileSync(
70 "git",
71 ["log", `${oldTag}..${newTag}`, "--format=DIVIDER\n%H|||%B"],
72 { encoding: "utf8" },
73 )
74 .replace(/\r\n/g, "\n")
75 .split("DIVIDER\n")
76 .filter((commit) => commit.length > 0)
77 .map((commit) => {
78 let [hash, firstLine] = commit.split("\n")[0].split("|||");
79 let cherryPick = firstLine.match(/\(cherry-pick #([0-9]+)\)/)?.[1] || "";
80 let pr = firstLine.match(/\(#(\d+)\)$/)?.[1] || "";
81 let releaseNotes = (commit.split(/Release notes:.*\n/i)[1] || "")
82 .split("\n\n")[0]
83 .trim()
84 .replace(/\n(?![\n-])/g, " ");
85
86 if (releaseNotes.includes("<public_issue_number_if_exists>")) {
87 releaseNotes = "";
88 }
89
90 return {
91 hash,
92 pr,
93 cherryPick,
94 releaseNotes,
95 firstLine,
96 };
97 });
98
99 return pullRequestNumbers;
100}
101
102function tagExists(tag) {
103 try {
104 execFileSync("git", ["rev-parse", "--verify", tag]);
105 return true;
106 } catch (e) {
107 return false;
108 }
109}