Skip to content

Commit c98084e

Browse files
author
Orta Therox
authored
Merge pull request #1056 from microsoft/changelogs_per_release
Generate changelogs per release
2 parents 2a099ed + d65f35b commit c98084e

File tree

4 files changed

+132
-97
lines changed

4 files changed

+132
-97
lines changed

deploy/createTypesPackages.mjs

Lines changed: 5 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -3,7 +3,7 @@
33
// node deploy/createTypesPackages.mjs
44

55
// prettier-ignore
6-
const packages = [
6+
export const packages = [
77
{
88
name: "@types/web",
99
description: "Types for the DOM, and other web technologies in browsers",
@@ -71,8 +71,6 @@ const go = async () => {
7171
}
7272
};
7373

74-
go();
75-
7674
async function updatePackageJSON(packagePath, pkg, gitSha) {
7775
const pkgJSONPath = join(packagePath, "package.json");
7876
const packageText = fs.readFileSync(pkgJSONPath, "utf8");
@@ -131,3 +129,7 @@ function copyREADME(pkg, pkgJSON, writePath) {
131129

132130
fs.writeFileSync(writePath, readme);
133131
}
132+
133+
if (process.argv[1] === fileURLToPath(import.meta.url)) {
134+
await go();
135+
}

deploy/deployChangedPackages.mjs

Lines changed: 111 additions & 85 deletions
Original file line numberDiff line numberDiff line change
@@ -6,110 +6,124 @@
66
// ones which have changed.
77

88
import * as fs from "fs";
9-
import { join, dirname } from "path";
9+
import { join, dirname, basename } from "path";
1010
import { fileURLToPath } from "url";
11-
import fetch from "node-fetch";
12-
import { spawnSync } from "child_process";
11+
import { spawnSync, execSync } from "child_process";
1312
import { Octokit } from "@octokit/core";
1413
import printDiff from "print-diff";
14+
import { generateChangelogFrom } from "../lib/changelog.js";
15+
import { packages } from "./createTypesPackages.mjs";
1516

1617
// eslint-disable-next-line @typescript-eslint/ban-ts-comment
1718
// @ts-ignore
1819
const __filename = fileURLToPath(import.meta.url);
1920
const __dirname = dirname(__filename);
2021

21-
const verify = () => {
22-
const authToken = process.env.GITHUB_TOKEN || process.env.GITHUB_API_TOKEN;
23-
if (!authToken)
24-
throw new Error(
25-
"There isn't an ENV var set up for creating a GitHub release, expected GITHUB_TOKEN."
22+
verify();
23+
24+
const uploaded = [];
25+
26+
// Loop through generated packages, deploying versions for anything which has different
27+
// .d.ts files from the version available on npm.
28+
const generatedDir = join(__dirname, "generated");
29+
for (const dirName of fs.readdirSync(generatedDir)) {
30+
console.log(`Looking at ${dirName}`);
31+
const localPackageJSONPath = join(generatedDir, dirName, "package.json");
32+
const newTSConfig = fs.readFileSync(localPackageJSONPath, "utf-8");
33+
const pkgJSON = JSON.parse(newTSConfig);
34+
35+
// We'll need to map back from the filename in the npm package to the
36+
// generated file in baselines inside the git tag
37+
const thisPackageMeta = packages.find((p) => p.name === pkgJSON.name);
38+
39+
const dtsFiles = fs
40+
.readdirSync(join(generatedDir, dirName))
41+
.filter((f) => f.endsWith(".d.ts"));
42+
43+
/** @type {string[]} */
44+
let releaseNotes = [];
45+
46+
// Look through each .d.ts file included in a package to
47+
// determine if anything has changed
48+
let upload = false;
49+
for (const file of dtsFiles) {
50+
const originalFilename = basename(
51+
thisPackageMeta.files.find((f) => f.to === file).from
2652
);
27-
};
28-
29-
const go = async () => {
30-
verify();
31-
32-
const uploaded = [];
33-
34-
// Loop through generated packages, deploying versions for anything which has different
35-
// .d.ts files from the version available on npm.
36-
const generatedDir = join(__dirname, "generated");
37-
for (const dirName of fs.readdirSync(generatedDir)) {
38-
console.log(`Looking at ${dirName}`);
39-
const localPackageJSONPath = join(generatedDir, dirName, "package.json");
40-
const newTSConfig = fs.readFileSync(localPackageJSONPath, "utf-8");
41-
const pkgJSON = JSON.parse(newTSConfig);
42-
43-
const dtsFiles = fs
44-
.readdirSync(join(generatedDir, dirName))
45-
.filter((f) => f.endsWith(".d.ts"));
46-
47-
// Look through each .d.ts file included in a package to
48-
// determine if anything has changed
49-
let upload = false;
50-
for (const file of dtsFiles) {
51-
const generatedDTSPath = join(generatedDir, dirName, file);
52-
const generatedDTSContent = fs.readFileSync(generatedDTSPath, "utf8");
53-
const unpkgURL = `https://unpkg.com/${pkgJSON.name}/${file}`;
54-
try {
55-
const npmDTSReq = await fetch(unpkgURL);
56-
const npmDTSText = await npmDTSReq.text();
57-
console.log(`Comparing ${file} from unpkg, to generated version:`);
58-
printDiff(npmDTSText, generatedDTSContent);
59-
60-
upload = upload || npmDTSText !== generatedDTSContent;
61-
} catch (error) {
62-
// Could not find a previous build
63-
console.log(`
64-
Could not get the file ${file} inside the npm package ${pkgJSON.name} from unpkg at ${unpkgURL}
53+
54+
const generatedDTSPath = join(generatedDir, dirName, file);
55+
const generatedDTSContent = fs.readFileSync(generatedDTSPath, "utf8");
56+
57+
// This assumes we'll only _ever_ ship patches, which may change in the
58+
// future someday.
59+
const [maj, min, patch] = pkgJSON.version.split(".");
60+
const olderVersion = `${maj}.${min}.${patch - 1}`;
61+
62+
try {
63+
const oldFile = gitShowFile(
64+
`${pkgJSON.name}@${olderVersion}`,
65+
`baselines/${originalFilename}`
66+
);
67+
console.log(`Comparing ${file} from ${olderVersion}, to now:`);
68+
printDiff(oldFile, generatedDTSContent);
69+
70+
const title = `\n## \`${file}\`\n`;
71+
const notes = generateChangelogFrom(oldFile, generatedDTSContent);
72+
releaseNotes.push(title);
73+
releaseNotes.push(notes.trim() === "" ? "No changes" : notes);
74+
75+
upload = upload || oldFile !== generatedDTSContent;
76+
} catch (error) {
77+
// Could not find a previous build
78+
console.log(`
79+
Could not get the file ${file} inside the npm package ${pkgJSON.name} from tag ${olderVersion}.
6580
Assuming that this means we need to upload this package.`);
66-
upload = true;
67-
}
81+
upload = true;
6882
}
83+
}
6984

70-
// Publish via npm
71-
if (upload) {
72-
if (process.env.NODE_AUTH_TOKEN) {
73-
const publish = spawnSync("npm", ["publish", "--access", "public"], {
74-
cwd: join(generatedDir, dirName),
75-
stdio: "inherit",
76-
});
77-
78-
if (publish.status) {
79-
console.log(publish.stdout?.toString());
80-
console.log(publish.stderr?.toString());
81-
process.exit(publish.status);
82-
} else {
83-
console.log(publish.stdout?.toString());
84-
85-
await createRelease(`${pkgJSON.name}@${pkgJSON.version}`);
86-
}
85+
// Publish via npm
86+
if (upload) {
87+
if (process.env.NODE_AUTH_TOKEN) {
88+
const publish = spawnSync("npm", ["publish", "--access", "public"], {
89+
cwd: join(generatedDir, dirName),
90+
stdio: "inherit",
91+
});
92+
93+
if (publish.status) {
94+
console.log(publish.stdout?.toString());
95+
console.log(publish.stderr?.toString());
96+
process.exit(publish.status);
8797
} else {
88-
console.log(
89-
"Wanting to run: 'npm publish --access public' in " +
90-
join(generatedDir, dirName)
91-
);
92-
}
98+
console.log(publish.stdout?.toString());
9399

94-
uploaded.push(dirName);
100+
await createRelease(`${pkgJSON.name}@${pkgJSON.version}`);
101+
}
102+
} else {
103+
console.log(
104+
"Wanting to run: 'npm publish --access public' in " +
105+
join(generatedDir, dirName)
106+
);
95107
}
96-
}
97108

98-
// Warn if we did a dry run.
99-
if (!process.env.NODE_AUTH_TOKEN) {
100-
console.log(
101-
"Did a dry run because process.env.NODE_AUTH_TOKEN is not set."
102-
);
109+
uploaded.push(dirName);
103110
}
104111

105-
if (uploaded.length) {
106-
console.log("Uploaded: ", uploaded.join(", "));
107-
} else {
108-
console.log("No uploads");
109-
}
110-
};
112+
console.log("\n# Release notes:");
113+
console.log(releaseNotes.join("\n"), "\n\n");
114+
}
115+
// Warn if we did a dry run.
116+
if (!process.env.NODE_AUTH_TOKEN) {
117+
console.log("Did a dry run because process.env.NODE_AUTH_TOKEN is not set.");
118+
}
119+
120+
if (uploaded.length) {
121+
console.log("Uploaded: ", uploaded.join(", "));
122+
} else {
123+
console.log("No uploads");
124+
}
111125

112-
async function createRelease(tag) {
126+
async function createRelease(tag, body) {
113127
const authToken = process.env.GITHUB_TOKEN || process.env.GITHUB_API_TOKEN;
114128
const octokit = new Octokit({ auth: authToken });
115129

@@ -119,6 +133,8 @@ async function createRelease(tag) {
119133
repo: "TypeScript-DOM-lib-generator",
120134
tag_name: tag,
121135
target_commitish: process.env.GITHUB_SHA,
136+
name: tag,
137+
body,
122138
});
123139
} catch (error) {
124140
console.error(
@@ -127,4 +143,14 @@ async function createRelease(tag) {
127143
}
128144
}
129145

130-
go();
146+
function verify() {
147+
const authToken = process.env.GITHUB_TOKEN || process.env.GITHUB_API_TOKEN;
148+
if (!authToken)
149+
throw new Error(
150+
"There isn't an ENV var set up for creating a GitHub release, expected GITHUB_TOKEN."
151+
);
152+
}
153+
154+
function gitShowFile(commitish, path) {
155+
return execSync(`git show "${commitish}":${path}`, { encoding: "utf-8" });
156+
}

src/changelog.ts

Lines changed: 14 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -137,10 +137,21 @@ function writeAddedRemovedInline(added: Set<string>, removed: Set<string>) {
137137

138138
const dom = "baselines/dom.generated.d.ts";
139139

140-
export function generate(): string {
140+
export function generateDefaultFromRecentTag(): string {
141141
const [base = gitLatestTag(), head = "HEAD"] = process.argv.slice(2);
142142
const previous = gitShowFile(base, dom);
143143
const current = gitShowFile(head, dom);
144+
const changelog = generateChangelogFrom(previous, current);
145+
if (!changelog.length) {
146+
throw new Error(`No change reported between ${base} and ${head}.`);
147+
}
148+
return changelog;
149+
}
150+
151+
export function generateChangelogFrom(
152+
previous: string,
153+
current: string
154+
): string {
144155
const {
145156
interfaces: { added, removed, modified },
146157
others,
@@ -150,6 +161,7 @@ export function generate(): string {
150161
if (added.size || removed.size) {
151162
outputs.push(writeAddedRemoved(added, removed));
152163
}
164+
153165
if (modified.size) {
154166
const modifiedOutput = [`## Modified\n`];
155167
for (const [key, value] of modified.entries()) {
@@ -169,14 +181,9 @@ export function generate(): string {
169181
}
170182

171183
const output = outputs.join("\n\n");
172-
173-
if (!output.length) {
174-
throw new Error(`No change reported between ${base} and ${head}.`);
175-
}
176-
177184
return output;
178185
}
179186

180187
if (process.argv[1] === fileURLToPath(import.meta.url)) {
181-
console.log(generate());
188+
console.log(generateDefaultFromRecentTag());
182189
}

src/version.ts

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,8 +1,8 @@
11
import { execSync } from "child_process";
22
import { readFile, writeFile } from "fs/promises";
3-
import { generate } from "./changelog.js";
3+
import { generateDefaultFromRecentTag } from "./changelog.js";
44

5-
const output = generate();
5+
const output = generateDefaultFromRecentTag();
66

77
const path = new URL("../CHANGELOG.md", import.meta.url);
88

0 commit comments

Comments
 (0)