Skip to content

Generate changelogs per release #1056

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Merged
merged 5 commits into from
Jul 9, 2021
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
8 changes: 5 additions & 3 deletions deploy/createTypesPackages.mjs
Original file line number Diff line number Diff line change
Expand Up @@ -3,7 +3,7 @@
// node deploy/createTypesPackages.mjs

// prettier-ignore
const packages = [
export const packages = [
{
name: "@types/web",
description: "Types for the DOM, and other web technologies in browsers",
Expand Down Expand Up @@ -71,8 +71,6 @@ const go = async () => {
}
};

go();

async function updatePackageJSON(packagePath, pkg, gitSha) {
const pkgJSONPath = join(packagePath, "package.json");
const packageText = fs.readFileSync(pkgJSONPath, "utf8");
Expand Down Expand Up @@ -131,3 +129,7 @@ function copyREADME(pkg, pkgJSON, writePath) {

fs.writeFileSync(writePath, readme);
}

if (process.argv[1] === fileURLToPath(import.meta.url)) {
await go();
}
196 changes: 111 additions & 85 deletions deploy/deployChangedPackages.mjs
Original file line number Diff line number Diff line change
Expand Up @@ -6,110 +6,124 @@
// ones which have changed.

import * as fs from "fs";
import { join, dirname } from "path";
import { join, dirname, basename } from "path";
import { fileURLToPath } from "url";
import fetch from "node-fetch";
import { spawnSync } from "child_process";
import { spawnSync, execSync } from "child_process";
import { Octokit } from "@octokit/core";
import printDiff from "print-diff";
import { generateChangelogFrom } from "../lib/changelog.js";
import { packages } from "./createTypesPackages.mjs";

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

const verify = () => {
const authToken = process.env.GITHUB_TOKEN || process.env.GITHUB_API_TOKEN;
if (!authToken)
throw new Error(
"There isn't an ENV var set up for creating a GitHub release, expected GITHUB_TOKEN."
verify();

const uploaded = [];

// Loop through generated packages, deploying versions for anything which has different
// .d.ts files from the version available on npm.
const generatedDir = join(__dirname, "generated");
for (const dirName of fs.readdirSync(generatedDir)) {
console.log(`Looking at ${dirName}`);
const localPackageJSONPath = join(generatedDir, dirName, "package.json");
const newTSConfig = fs.readFileSync(localPackageJSONPath, "utf-8");
const pkgJSON = JSON.parse(newTSConfig);

// We'll need to map back from the filename in the npm package to the
// generated file in baselines inside the git tag
const thisPackageMeta = packages.find((p) => p.name === pkgJSON.name);

const dtsFiles = fs
.readdirSync(join(generatedDir, dirName))
.filter((f) => f.endsWith(".d.ts"));

/** @type {string[]} */
let releaseNotes = [];

// Look through each .d.ts file included in a package to
// determine if anything has changed
let upload = false;
for (const file of dtsFiles) {
const originalFilename = basename(
thisPackageMeta.files.find((f) => f.to === file).from
);
};

const go = async () => {
verify();

const uploaded = [];

// Loop through generated packages, deploying versions for anything which has different
// .d.ts files from the version available on npm.
const generatedDir = join(__dirname, "generated");
for (const dirName of fs.readdirSync(generatedDir)) {
console.log(`Looking at ${dirName}`);
const localPackageJSONPath = join(generatedDir, dirName, "package.json");
const newTSConfig = fs.readFileSync(localPackageJSONPath, "utf-8");
const pkgJSON = JSON.parse(newTSConfig);

const dtsFiles = fs
.readdirSync(join(generatedDir, dirName))
.filter((f) => f.endsWith(".d.ts"));

// Look through each .d.ts file included in a package to
// determine if anything has changed
let upload = false;
for (const file of dtsFiles) {
const generatedDTSPath = join(generatedDir, dirName, file);
const generatedDTSContent = fs.readFileSync(generatedDTSPath, "utf8");
const unpkgURL = `https://unpkg.com/${pkgJSON.name}/${file}`;
try {
const npmDTSReq = await fetch(unpkgURL);
const npmDTSText = await npmDTSReq.text();
console.log(`Comparing ${file} from unpkg, to generated version:`);
printDiff(npmDTSText, generatedDTSContent);

upload = upload || npmDTSText !== generatedDTSContent;
} catch (error) {
// Could not find a previous build
console.log(`
Could not get the file ${file} inside the npm package ${pkgJSON.name} from unpkg at ${unpkgURL}

const generatedDTSPath = join(generatedDir, dirName, file);
const generatedDTSContent = fs.readFileSync(generatedDTSPath, "utf8");

// This assumes we'll only _ever_ ship patches, which may change in the
// future someday.
const [maj, min, patch] = pkgJSON.version.split(".");
const olderVersion = `${maj}.${min}.${patch - 1}`;

try {
const oldFile = gitShowFile(
`${pkgJSON.name}@${olderVersion}`,
`baselines/${originalFilename}`
);
console.log(`Comparing ${file} from ${olderVersion}, to now:`);
printDiff(oldFile, generatedDTSContent);

const title = `\n## \`${file}\`\n`;
const notes = generateChangelogFrom(oldFile, generatedDTSContent);
releaseNotes.push(title);
releaseNotes.push(notes.trim() === "" ? "No changes" : notes);

upload = upload || oldFile !== generatedDTSContent;
} catch (error) {
// Could not find a previous build
console.log(`
Could not get the file ${file} inside the npm package ${pkgJSON.name} from tag ${olderVersion}.
Assuming that this means we need to upload this package.`);
upload = true;
}
upload = true;
}
}

// Publish via npm
if (upload) {
if (process.env.NODE_AUTH_TOKEN) {
const publish = spawnSync("npm", ["publish", "--access", "public"], {
cwd: join(generatedDir, dirName),
stdio: "inherit",
});

if (publish.status) {
console.log(publish.stdout?.toString());
console.log(publish.stderr?.toString());
process.exit(publish.status);
} else {
console.log(publish.stdout?.toString());

await createRelease(`${pkgJSON.name}@${pkgJSON.version}`);
}
// Publish via npm
if (upload) {
if (process.env.NODE_AUTH_TOKEN) {
const publish = spawnSync("npm", ["publish", "--access", "public"], {
cwd: join(generatedDir, dirName),
stdio: "inherit",
});

if (publish.status) {
console.log(publish.stdout?.toString());
console.log(publish.stderr?.toString());
process.exit(publish.status);
} else {
console.log(
"Wanting to run: 'npm publish --access public' in " +
join(generatedDir, dirName)
);
}
console.log(publish.stdout?.toString());

uploaded.push(dirName);
await createRelease(`${pkgJSON.name}@${pkgJSON.version}`);
}
} else {
console.log(
"Wanting to run: 'npm publish --access public' in " +
join(generatedDir, dirName)
);
}
}

// Warn if we did a dry run.
if (!process.env.NODE_AUTH_TOKEN) {
console.log(
"Did a dry run because process.env.NODE_AUTH_TOKEN is not set."
);
uploaded.push(dirName);
}

if (uploaded.length) {
console.log("Uploaded: ", uploaded.join(", "));
} else {
console.log("No uploads");
}
};
console.log("\n# Release notes:");
console.log(releaseNotes.join("\n"), "\n\n");
}
// Warn if we did a dry run.
if (!process.env.NODE_AUTH_TOKEN) {
console.log("Did a dry run because process.env.NODE_AUTH_TOKEN is not set.");
}

if (uploaded.length) {
console.log("Uploaded: ", uploaded.join(", "));
} else {
console.log("No uploads");
}

async function createRelease(tag) {
async function createRelease(tag, body) {
const authToken = process.env.GITHUB_TOKEN || process.env.GITHUB_API_TOKEN;
const octokit = new Octokit({ auth: authToken });

Expand All @@ -119,6 +133,8 @@ async function createRelease(tag) {
repo: "TypeScript-DOM-lib-generator",
tag_name: tag,
target_commitish: process.env.GITHUB_SHA,
name: tag,
body,
});
} catch (error) {
console.error(
Expand All @@ -127,4 +143,14 @@ async function createRelease(tag) {
}
}

go();
function verify() {
const authToken = process.env.GITHUB_TOKEN || process.env.GITHUB_API_TOKEN;
if (!authToken)
throw new Error(
"There isn't an ENV var set up for creating a GitHub release, expected GITHUB_TOKEN."
);
}

function gitShowFile(commitish, path) {
return execSync(`git show "${commitish}":${path}`, { encoding: "utf-8" });
}
21 changes: 14 additions & 7 deletions src/changelog.ts
Original file line number Diff line number Diff line change
Expand Up @@ -137,10 +137,21 @@ function writeAddedRemovedInline(added: Set<string>, removed: Set<string>) {

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

export function generate(): string {
export function generateDefaultFromRecentTag(): string {
const [base = gitLatestTag(), head = "HEAD"] = process.argv.slice(2);
const previous = gitShowFile(base, dom);
const current = gitShowFile(head, dom);
const changelog = generateChangelogFrom(previous, current);
if (!changelog.length) {
throw new Error(`No change reported between ${base} and ${head}.`);
}
return changelog;
}

export function generateChangelogFrom(
previous: string,
current: string
): string {
const {
interfaces: { added, removed, modified },
others,
Expand All @@ -150,6 +161,7 @@ export function generate(): string {
if (added.size || removed.size) {
outputs.push(writeAddedRemoved(added, removed));
}

if (modified.size) {
const modifiedOutput = [`## Modified\n`];
for (const [key, value] of modified.entries()) {
Expand All @@ -169,14 +181,9 @@ export function generate(): string {
}

const output = outputs.join("\n\n");

if (!output.length) {
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Could you move this logic to the caller?

throw new Error(`No change reported between ${base} and ${head}.`);
}

return output;
}

if (process.argv[1] === fileURLToPath(import.meta.url)) {
console.log(generate());
console.log(generateDefaultFromRecentTag());
}
4 changes: 2 additions & 2 deletions src/version.ts
Original file line number Diff line number Diff line change
@@ -1,8 +1,8 @@
import { execSync } from "child_process";
import { readFile, writeFile } from "fs/promises";
import { generate } from "./changelog.js";
import { generateDefaultFromRecentTag } from "./changelog.js";

const output = generate();
const output = generateDefaultFromRecentTag();

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

Expand Down