From 8298e8599f08a296dcf890096faa427eec74d2f6 Mon Sep 17 00:00:00 2001 From: Richard Moore Date: Wed, 22 Feb 2023 21:52:33 -0500 Subject: [PATCH] admin: added changelog generation scripts --- CHANGELOG.md | 9 +- package.json | 5 +- src.ts/_admin/generate-diffs.ts | 13 ++ src.ts/_admin/update-changelog.ts | 208 ++++++++++++++++++++++++++++++ src.ts/_admin/update-version.ts | 5 +- src.ts/_admin/utils/git.ts | 40 ++++-- src.ts/_admin/utils/npm.ts | 39 ++++++ src.ts/_admin/utils/path.ts | 1 - 8 files changed, 301 insertions(+), 19 deletions(-) create mode 100644 src.ts/_admin/generate-diffs.ts create mode 100644 src.ts/_admin/update-changelog.ts diff --git a/CHANGELOG.md b/CHANGELOG.md index 9179e3975..758078a45 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,7 +1,14 @@ 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) -------------------------------- diff --git a/package.json b/package.json index cdfc565a0..9356b65c3 100644 --- a/package.json +++ b/package.json @@ -105,7 +105,7 @@ "url": "https://www.buymeacoffee.com/ricmoo" } ], - "gitHead": "981a962543809345c9d38e196a1edc454fdce983", + "gitHead": "fb3dffcc1298c05036c06b4f5938ef092c0d7e60", "homepage": "https://ethers.org", "keywords": [ "ethereum", @@ -125,13 +125,12 @@ "url": "git://github.com/ethers-io/ethers.js.git" }, "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/", "_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", "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-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-dist": "npm run build && npm run _build-dist && npm run _dist-stats", "build-docs": "echo 'foo'", diff --git a/src.ts/_admin/generate-diffs.ts b/src.ts/_admin/generate-diffs.ts new file mode 100644 index 000000000..9959f4ee2 --- /dev/null +++ b/src.ts/_admin/generate-diffs.ts @@ -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); + } +})(); diff --git a/src.ts/_admin/update-changelog.ts b/src.ts/_admin/update-changelog.ts new file mode 100644 index 000000000..f2ea5544b --- /dev/null +++ b/src.ts/_admin/update-changelog.ts @@ -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; + commit: string; +}; + +type Version = { + date: string; + version: string; + changes: Array +}; + +async function getChanges(tag0: string, tag1: string): Promise> { + const result: Array = [ ]; + + 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 = [ ]; + + message = message.replace(/\((([0-9#,]|\s)*)\)/g, (all, text) => { + text = (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; +}; + +(async function() { + // Get the already included versions in the CHANGELOG + const present: Array = [ { 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 = [ ]; + 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 = [ ]; + 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")); +})(); diff --git a/src.ts/_admin/update-version.ts b/src.ts/_admin/update-version.ts index c802279a2..2f9d5c1ff 100644 --- a/src.ts/_admin/update-version.ts +++ b/src.ts/_admin/update-version.ts @@ -3,7 +3,7 @@ import semver from "semver"; import { FetchRequest } from "../utils/index.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 { resolve } from "./utils/path.js"; @@ -19,6 +19,7 @@ async function getNpmPackage(name: string): Promise { return cache[name] || null; } + 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`; atomicWrite(resolve("src.ts/_version.ts"), content); @@ -39,7 +40,7 @@ function writeVersion(version: string): void { const remoteGitHead = remotePkgInfo.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("tests:")) { continue; } gitHead = log.commit; diff --git a/src.ts/_admin/utils/git.ts b/src.ts/_admin/utils/git.ts index cdadafa3a..d8194f95c 100644 --- a/src.ts/_admin/utils/git.ts +++ b/src.ts/_admin/utils/git.ts @@ -44,32 +44,48 @@ export interface GitLog { body: string; } -export async function getGitLog(filename: string, limit?: number): Promise> { - if (limit == null) { limit = 100; } - const result = await run("git", [ "log", "-n", String(limit), "--", filename ]); - if (!result.ok) { throw new Error(`git log error`); } +export async function getLogs(files?: null | Array, range?: null | { tag0: string, tag1: string }, limit?: null | number): Promise> { + const args = [ "log", "-n", String((limit != null) ? limit: 100) ]; - 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 [ ]; } - const logs: Array = [ { commit: "", author: "", date: "", body: "" } ]; + const results: Array = [ { commit: "", author: "", date: "", body: "" } ]; for (const line of log.split("\n")) { const hashMatch = line.match(/^commit\s+([0-9a-f]{40})/i); if (hashMatch) { - logs.push({ commit: hashMatch[1], author: "", date: "", body: "" }); + results.push({ commit: hashMatch[1], author: "", date: "", body: "" }); } else { 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:")) { - logs[logs.length - 1].date = line.substring(5).trim(); + results[results.length - 1].date = line.substring(5).trim(); } 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 - logs.shift(); + results.shift(); - return logs; + return results; +} + +export async function getDiff(filename: string, tag0: string, tag1: string): Promise { + const result = await run("git", [ "diff", `${ tag0 }..${ tag1 }`, "--", filename ]); + if (!result.ok) { throw new Error(`git log error`); } + return result.stdout.trim(); } diff --git a/src.ts/_admin/utils/npm.ts b/src.ts/_admin/utils/npm.ts index 34d9410bf..90b9a6bbf 100644 --- a/src.ts/_admin/utils/npm.ts +++ b/src.ts/_admin/utils/npm.ts @@ -1,3 +1,42 @@ +import { FetchRequest } from "../../utils/index.js"; + +const cache: Record = { }; + +export async function _getNpmPackage(name: string): Promise { + 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> { + const result: Array = [ ]; + + 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"; diff --git a/src.ts/_admin/utils/path.ts b/src.ts/_admin/utils/path.ts index b81eeba8c..ed188ceae 100644 --- a/src.ts/_admin/utils/path.ts +++ b/src.ts/_admin/utils/path.ts @@ -5,7 +5,6 @@ const __filename = fileURLToPath(import.meta.url); const __dirname = dirname(__filename); export const ROOT = _resolve(__dirname, "../../../"); -console.log(ROOT); export function resolve(...args: Array): string { args = args.slice();