admin: added changelog generation scripts

This commit is contained in:
Richard Moore 2023-02-22 21:52:33 -05:00
parent fb3dffcc12
commit 8298e8599f
8 changed files with 301 additions and 19 deletions

@ -1,7 +1,14 @@
Change Log Change Log
========== ==========
This change log is maintained by `src.ts/_admin/update-changelog.ts but may also be manually updated.` This change log is maintained by `src.ts/_admin/update-changelog.ts` but may also be manually updated.
ethers/v6.0.6 (2023-02-22 21:51)
--------------------------------
- Added chain parameters for Arbitrum and Optimism ([#3811](https://github.com/ethers-io/ethers.js/issues/3811); [77a7323](https://github.com/ethers-io/ethers.js/commit/77a7323119923e596f4def4f1bc90beae5447320)).
- Fix NonceManager race condition ([#3812](https://github.com/ethers-io/ethers.js/issues/3812), [#3813](https://github.com/ethers-io/ethers.js/issues/3813); [5a3c10a](https://github.com/ethers-io/ethers.js/commit/5a3c10a29c047609a50828adb620d88aa8cf0014)).
- Add UMD output to dist builds ([#3814](https://github.com/ethers-io/ethers.js/issues/3814); [f9eed4c](https://github.com/ethers-io/ethers.js/commit/f9eed4cdb190b06dd4ddaa2382c1de42e8e98de6)).
ethers/v6.0.5 (2023-02-18 22:36) ethers/v6.0.5 (2023-02-18 22:36)
-------------------------------- --------------------------------

@ -105,7 +105,7 @@
"url": "https://www.buymeacoffee.com/ricmoo" "url": "https://www.buymeacoffee.com/ricmoo"
} }
], ],
"gitHead": "981a962543809345c9d38e196a1edc454fdce983", "gitHead": "fb3dffcc1298c05036c06b4f5938ef092c0d7e60",
"homepage": "https://ethers.org", "homepage": "https://ethers.org",
"keywords": [ "keywords": [
"ethereum", "ethereum",
@ -125,13 +125,12 @@
"url": "git://github.com/ethers-io/ethers.js.git" "url": "git://github.com/ethers-io/ethers.js.git"
}, },
"scripts": { "scripts": {
"_build-clean": "npm run clean && node lib.esm/_admin/update-version-const && npm run build-all && npm run _build-dist",
"_build-dist": "rollup -c && uglifyjs ./dist/ethers.js -o ./dist/ethers.min.js && uglifyjs ./dist/ethers.umd.js -o ./dist/ethers.umd.min.js && uglifyjs ./dist/wordlists-extra.js -o ./dist/wordlists-extra.min.js && cp ./output/post-build/dist/* ./dist/", "_build-dist": "rollup -c && uglifyjs ./dist/ethers.js -o ./dist/ethers.min.js && uglifyjs ./dist/ethers.umd.js -o ./dist/ethers.umd.min.js && uglifyjs ./dist/wordlists-extra.js -o ./dist/wordlists-extra.min.js && cp ./output/post-build/dist/* ./dist/",
"_dist-stats": "gzip -k9f -S '.gz' ./dist/ethers.min.js && gzip -k9f -S '.gz' ./dist/ethers.umd.min.js && gzip -k9f -S '.gz' ./dist/wordlists-extra.min.js && du -hs ./dist/*.gz && echo '' && du -hs ./dist/*.js", "_dist-stats": "gzip -k9f -S '.gz' ./dist/ethers.min.js && gzip -k9f -S '.gz' ./dist/ethers.umd.min.js && gzip -k9f -S '.gz' ./dist/wordlists-extra.min.js && du -hs ./dist/*.gz && echo '' && du -hs ./dist/*.js",
"auto-build": "npm run build -- -w", "auto-build": "npm run build -- -w",
"build": "tsc --project tsconfig.esm.json", "build": "tsc --project tsconfig.esm.json",
"build-all": "npm run build && cp ./output/post-build/lib.esm/* ./lib.esm/ && npm run build-commonjs && npm run build-types", "build-all": "npm run build && cp ./output/post-build/lib.esm/* ./lib.esm/ && npm run build-commonjs && npm run build-types",
"build-clean": "npm run clean && npm run build && node lib.esm/_admin/update-version.js && npm run build-all && npm run _build-dist && npm run _dist-stats", "build-clean": "npm run clean && npm run build && node lib.esm/_admin/update-version.js && node lib.esm/_admin/update-changelog.js && npm run build-all && npm run _build-dist && npm run _dist-stats",
"build-commonjs": "tsc --project tsconfig.commonjs.json && cp ./output/post-build/lib.commonjs/* ./lib.commonjs/", "build-commonjs": "tsc --project tsconfig.commonjs.json && cp ./output/post-build/lib.commonjs/* ./lib.commonjs/",
"build-dist": "npm run build && npm run _build-dist && npm run _dist-stats", "build-dist": "npm run build && npm run _build-dist && npm run _dist-stats",
"build-docs": "echo 'foo'", "build-docs": "echo 'foo'",

@ -0,0 +1,13 @@
import { getVersions } from "./utils/npm.js";
import { resolve } from "./utils/path.js";
import { getDiff } from "./utils/git.js";
(async function() {
let versions = await getVersions("ethers");
versions = versions.filter((h) => (h.version.match(/^6\.[0-9]+\.[0-9]+$/)));
for (let i = 1; i < versions.length; i++) {
const tag0 = versions[i - 1].gitHead, tag1 = versions[i].gitHead;
const diff = await getDiff(resolve("dist/ethers.js"), tag0, tag1);
console.log(diff);
}
})();

@ -0,0 +1,208 @@
import fs from "fs";
import { getLogs } from "./utils/git.js";
import { loadJson } from "./utils/json.js";
import { resolve } from "./utils/path.js";
import { getVersions } from "./utils/npm.js";
function repeat(c: string, length: number): string {
if (c.length === 0) { throw new Error("too short"); }
while(c.length < length) { c += c; }
return c.substring(0, length);
}
function zpad(value: number, length?: number): string {
if (length == null) { length = 2; }
const str = String(value);
return repeat("0", length - str.length) + str;
}
function getDate(date: Date): string {
return [
date.getFullYear(),
zpad(date.getMonth() + 1),
zpad(date.getDate())
].join("-");
}
export function getDateTime(date: Date): string {
return getDate(date) + " " + [
zpad(date.getHours()) ,
zpad(date.getMinutes() + 1)
].join(":");
}
type Change = {
message: string;
issues: Array<string>;
commit: string;
};
type Version = {
date: string;
version: string;
changes: Array<Change>
};
async function getChanges(tag0: string, tag1: string): Promise<Array<Change>> {
const result: Array<Change> = [ ];
const logs = await getLogs(null, { tag0, tag1 });
for (const log of logs) {
if (log.body.startsWith("admin:") || log.body.startsWith("docs:")) {
continue;
}
let message = log.body;
const issues: Array<string> = [ ];
message = message.replace(/\((([0-9#,]|\s)*)\)/g, (all, text) => {
text = (<string>text).replace(/#([0-9]+)/g, (all, issue) => {
issues.push(issue);
return "";
});
if (text.replace(/,/g, "").trim()) {
console.log(`WARNING: commit leftovers ${ JSON.stringify(text) }`);
}
return "";
}).replace(/\.+\s*$/, "").trim();
result.push({ message, issues, commit: log.commit });
}
return result;
}
type PresentVersion = {
version: string;
body: Array<string>;
};
(async function() {
// Get the already included versions in the CHANGELOG
const present: Array<PresentVersion> = [ { version: "null", body: [ ] } ];
{
const content = fs.readFileSync(resolve("CHANGELOG.md")).toString();
for (const line of content.split("\n")) {
let match = line.match(/^ethers\/v(\S+)\s/);
if (match) {
present.push({ version: match[1], body: [ line ] });
} else {
present[present.length - 1].body.push(line);
}
}
for (const { body } of present) {
while (body[body.length - 1].trim() === "") { body.pop(); }
}
}
// Get the remote versions (along with their date and gitHead)
let versions = await getVersions("ethers");
versions = versions.filter((v) => (v.version.match(/^6\.[0-9]+\.[0-9]+$/)));
const entries: Array<PresentVersion | Version> = [ ];
const getPresent = (version: string) => {
const pres = present.filter((p) => (p.version === version));
return pres.length ? pres[0]: null;
};
// Add the first entry, which has no previous version to compare against
{
const pres = getPresent(versions[0].version);
if (pres) {
entries.push(pres);
} else {
entries.push({
date: getDateTime(new Date(versions[0].date)),
version: versions[0].version,
changes: [ {
message: "Initial release",
issues: [ ],
commit: versions[0].gitHead
} ]
});
}
}
// Add each version, with preference given to present entries, as
// they may have been updated manually
let lastVer = versions[0];
for (let i = 1; i < versions.length; i++) {
const ver = versions[i];
// Prefer present entries
const pres = getPresent(ver.version);
if (pres) {
entries.push(pres);
lastVer = ver;
continue;
}
// Get the entry info from git
const version = ver.version;
const date = getDateTime(new Date(ver.date));
const changes = await getChanges(lastVer.gitHead, ver.gitHead);
entries.push({ date, version, changes });
lastVer = ver;
}
// If this is a new version (not present in npm) add the changes
// from the lastest version until HEAD.
const curVer = loadJson(resolve("package.json")).version;
if (curVer !== lastVer) {
// Include any present entry, as it was placed here by a
// previous run of update-changelog and may have been
// modified manually
const pres = getPresent(curVer);
if (pres) {
console.log(`WARNING: existing entry for ${ curVer }; duplicating`);
entries.push(pres);
}
// Also include theentry from git
const latest = await getChanges(lastVer.gitHead, "HEAD");
if (latest.length) {
entries.push({
date: getDateTime(new Date()),
version: curVer,
changes: latest
});
}
}
// Gerenate the CHANGELOG.md output
const output: Array<string> = [ ];
output.push("Change Log");
output.push("==========");
output.push("");
output.push("This change log is maintained by `src.ts/_admin/update-changelog.ts` but may also be manually updated.");
output.push("");
for (const ver of entries.reverse()) {
// Present entry; include verbatim
if ("body" in ver) {
ver.body.forEach((line) => output.push(line));
output.push("")
continue;
}
// Entry from git; format it nicely
const title = `ethers/v${ ver.version } (${ ver.date})`;
output.push(title);
output.push(repeat("-", title.length));
output.push("");
for (const change of ver.changes) {
let line = ` - ${ change.message } (`;
line += change.issues.map((i) => {
return `[#${ i }](https:/\/github.com/ethers-io/ethers.js/issues/${ i })`;
}).join(", ");
if (change.issues.length) { line += "; "; }
line += `[${ change.commit.substring(0, 7) }](https:/\/github.com/ethers-io/ethers.js/commit/${ change.commit })).`;
output.push(line)
}
output.push("");
}
fs.writeFileSync(resolve("CHANGELOG.md"), output.join("\n"));
})();

@ -3,7 +3,7 @@ import semver from "semver";
import { FetchRequest } from "../utils/index.js"; import { FetchRequest } from "../utils/index.js";
import { atomicWrite } from "./utils/fs.js"; import { atomicWrite } from "./utils/fs.js";
import { getGitLog } from "./utils/git.js"; import { getLogs } from "./utils/git.js";
import { loadJson, saveJson } from "./utils/json.js"; import { loadJson, saveJson } from "./utils/json.js";
import { resolve } from "./utils/path.js"; import { resolve } from "./utils/path.js";
@ -19,6 +19,7 @@ async function getNpmPackage(name: string): Promise<any> {
return cache[name] || null; return cache[name] || null;
} }
function writeVersion(version: string): void { function writeVersion(version: string): void {
const content = `/* Do NOT modify this file; see /src.ts/_admin/update-version.ts */\n\n/**\n * The current version of Ethers.\n */\nexport const version: string = "${ version }";\n`; const content = `/* Do NOT modify this file; see /src.ts/_admin/update-version.ts */\n\n/**\n * The current version of Ethers.\n */\nexport const version: string = "${ version }";\n`;
atomicWrite(resolve("src.ts/_version.ts"), content); atomicWrite(resolve("src.ts/_version.ts"), content);
@ -39,7 +40,7 @@ function writeVersion(version: string): void {
const remoteGitHead = remotePkgInfo.gitHead; const remoteGitHead = remotePkgInfo.gitHead;
let gitHead = ""; let gitHead = "";
for (const log of await getGitLog(".")) { for (const log of await getLogs([ "." ])) {
if (log.body.startsWith("admin:")) { continue; } if (log.body.startsWith("admin:")) { continue; }
if (log.body.startsWith("tests:")) { continue; } if (log.body.startsWith("tests:")) { continue; }
gitHead = log.commit; gitHead = log.commit;

@ -44,32 +44,48 @@ export interface GitLog {
body: string; body: string;
} }
export async function getGitLog(filename: string, limit?: number): Promise<Array<GitLog>> { export async function getLogs(files?: null | Array<string>, range?: null | { tag0: string, tag1: string }, limit?: null | number): Promise<Array<GitLog>> {
if (limit == null) { limit = 100; } const args = [ "log", "-n", String((limit != null) ? limit: 100) ];
const result = await run("git", [ "log", "-n", String(limit), "--", filename ]);
if (!result.ok) { throw new Error(`git log error`); }
let log = result.stdout.trim(); if (range) {
args.push(`${ range.tag0 }..${ range.tag1 }`);
}
if (files) {
args.push("--");
files.forEach((f) => args.push(f));
}
const exec = await run("git", args);
if (!exec.ok) { throw new Error(`git log error`); }
const log = exec.stdout.trim();
if (!log) { return [ ]; } if (!log) { return [ ]; }
const logs: Array<GitLog> = [ { commit: "", author: "", date: "", body: "" } ]; const results: Array<GitLog> = [ { commit: "", author: "", date: "", body: "" } ];
for (const line of log.split("\n")) { for (const line of log.split("\n")) {
const hashMatch = line.match(/^commit\s+([0-9a-f]{40})/i); const hashMatch = line.match(/^commit\s+([0-9a-f]{40})/i);
if (hashMatch) { if (hashMatch) {
logs.push({ commit: hashMatch[1], author: "", date: "", body: "" }); results.push({ commit: hashMatch[1], author: "", date: "", body: "" });
} else { } else {
if (line.startsWith("Author:")) { if (line.startsWith("Author:")) {
logs[logs.length - 1].author = line.substring(7).trim(); results[results.length - 1].author = line.substring(7).trim();
} else if (line.startsWith("Date:")) { } else if (line.startsWith("Date:")) {
logs[logs.length - 1].date = line.substring(5).trim(); results[results.length - 1].date = line.substring(5).trim();
} else { } else {
logs[logs.length - 1].body = (logs[logs.length - 1].body + " " + line).trim(); results[results.length - 1].body = (results[results.length - 1].body + " " + line).trim();
} }
} }
} }
// Nix the bootstrap entry // Nix the bootstrap entry
logs.shift(); results.shift();
return logs; return results;
}
export async function getDiff(filename: string, tag0: string, tag1: string): Promise<string> {
const result = await run("git", [ "diff", `${ tag0 }..${ tag1 }`, "--", filename ]);
if (!result.ok) { throw new Error(`git log error`); }
return result.stdout.trim();
} }

@ -1,3 +1,42 @@
import { FetchRequest } from "../../utils/index.js";
const cache: Record<string, any> = { };
export async function _getNpmPackage(name: string): Promise<any> {
if (!cache[name]) {
const resp = await (new FetchRequest("https:/\/registry.npmjs.org/" + name)).send();
resp.assertOk();
cache[name] = resp.bodyJson;
}
return cache[name] || null;
}
export type Version = {
version: string;
gitHead: string;
date: string;
};
export async function getVersions(name: string): Promise<Array<Version>> {
const result: Array<Version> = [ ];
const pkg = await _getNpmPackage(name);
for (const version in pkg.versions) {
const gitHead = pkg.versions[version].gitHead;
const date = pkg.time[version];
if (gitHead == null || date == null) { continue; }
result.push({ date, gitHead, version });
}
return result;
}
/*
(async function() {
//console.log(await _getNpmPackage("ethers"));
console.log(await getGitHeads("ethers"));
})();
*/
/* /*
import semver from "semver"; import semver from "semver";

@ -5,7 +5,6 @@ const __filename = fileURLToPath(import.meta.url);
const __dirname = dirname(__filename); const __dirname = dirname(__filename);
export const ROOT = _resolve(__dirname, "../../../"); export const ROOT = _resolve(__dirname, "../../../");
console.log(ROOT);
export function resolve(...args: Array<string>): string { export function resolve(...args: Array<string>): string {
args = args.slice(); args = args.slice();