Skip to content

Commit fa6de38

Browse files
committed
chore: add a changelog generation script
The intent here is to speed up the versioning and changelog generation. We leverage changeset under the hood, but work around its issues with pre-release config and peer dependencies.
1 parent f17150b commit fa6de38

File tree

2 files changed

+248
-1
lines changed

2 files changed

+248
-1
lines changed

.changeset/config.json

+1-1
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,6 @@
11
{
22
"$schema": "https://unpkg.com/@changesets/[email protected]/schema.json",
3-
"changelog": "@changesets/cli/changelog",
3+
"changelog": false,
44
"commit": false,
55
"linked": [],
66
"access": "public",

scripts/generate-changelog.mjs

+247
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,247 @@
1+
import { readdir, readFile, writeFile, stat } from "node:fs/promises";
2+
import path from "node:path";
3+
import { exec } from "node:child_process";
4+
import { promisify } from "node:util";
5+
6+
const execAsync = promisify(exec);
7+
8+
const changesetDir = ".changeset";
9+
const packagesDir = "v-next";
10+
11+
async function main() {
12+
const changesets = await readAllNewChangsets();
13+
14+
validateChangesets(changesets);
15+
16+
const currentHardhatAlphaVersion = await readCurrentHardhatAlphaVersion();
17+
const nextHardhatAlphaVersion = incrementHardhatAlphaVersion(
18+
currentHardhatAlphaVersion
19+
);
20+
21+
await createAllPackageChangesetFor(nextHardhatAlphaVersion);
22+
23+
await executeChangesetVersion();
24+
25+
await updateHardhatChangelog(nextHardhatAlphaVersion, changesets);
26+
}
27+
28+
// Helper functions
29+
30+
async function readAllNewChangsets() {
31+
const allChangesetNames = (await readdir(changesetDir))
32+
.filter((file) => file.endsWith(".md"))
33+
.map((file) => file.slice(0, -3));
34+
35+
const alreadyAppliedChangesetNames = JSON.parse(
36+
await readFile(path.join(changesetDir, "pre.json"))
37+
);
38+
39+
const newChangesetNames = allChangesetNames.filter(
40+
(name) => !alreadyAppliedChangesetNames.changesets.includes(name)
41+
);
42+
43+
const changesets = [];
44+
45+
for (const newChangeSetName of newChangesetNames) {
46+
const changesetFilePath = path.join(changesetDir, `${newChangeSetName}.md`);
47+
48+
const changesetContent = await readFile(changesetFilePath, "utf-8");
49+
50+
const { content, frontMatter } = parseFrontMatter(changesetContent);
51+
const commitHash = await getAddingCommit(changesetFilePath);
52+
53+
changesets.push({
54+
frontMatter,
55+
content,
56+
path: changesetFilePath,
57+
commitHash,
58+
});
59+
}
60+
61+
return changesets;
62+
}
63+
64+
function validateChangesets(changesets) {
65+
if (changesets.length === 0) {
66+
console.log("Error: No new changesets found.");
67+
process.exit(1);
68+
}
69+
70+
let validationFailed = false;
71+
72+
for (const { frontMatter, path: changesetPath } of changesets) {
73+
if (!/^\s*"hardhat": patch$/m.test(frontMatter)) {
74+
validationFailed = true;
75+
console.log(
76+
`Error: ${changesetPath}: No "hardhat: patch", every Alpha changeset must include hardhat`
77+
);
78+
}
79+
80+
if (/: (major|minor)\s*$/m.test(frontMatter)) {
81+
validationFailed = true;
82+
console.log(
83+
`Error: ${changesetPath}: No "major" or "minor" changesets are allowed in Alpha`
84+
);
85+
}
86+
}
87+
88+
if (validationFailed) {
89+
process.exit(1);
90+
}
91+
}
92+
93+
async function readCurrentHardhatAlphaVersion() {
94+
const hardhatPackageJson = JSON.parse(
95+
await readFile(path.join("v-next", "hardhat", "package.json"))
96+
);
97+
98+
return hardhatPackageJson.version;
99+
}
100+
101+
function incrementHardhatAlphaVersion(version) {
102+
const match = version.match(/(\d+\.\d+\.\d+)-next\.(\d+)/);
103+
104+
if (!match) {
105+
console.log(`Unsupported version format: ${version}`);
106+
process.exit(1);
107+
}
108+
109+
const [, base, num] = match;
110+
const nextNum = Number(num) + 1;
111+
112+
return `${base}-next.${nextNum}`;
113+
}
114+
115+
async function createAllPackageChangesetFor(nextHardhatAlphaVersion) {
116+
const releaseChangesetPath = path.join(
117+
changesetDir,
118+
`release-${nextHardhatAlphaVersion}.md`
119+
);
120+
121+
const packageNames = await readAllPackageNames();
122+
123+
const releaseChangesetContent = `---
124+
${packageNames
125+
.filter((name) => name !== "hardhat")
126+
.map((name) => `"${name}": patch`)
127+
.join("\n")}
128+
---
129+
130+
Hardhat 3 Alpha release (${new Date().toISOString()})
131+
`;
132+
133+
await writeFile(releaseChangesetPath, releaseChangesetContent);
134+
}
135+
136+
async function executeChangesetVersion() {
137+
await execAsync("pnpm changeset version");
138+
await execAsync("pnpm install");
139+
}
140+
141+
async function updateHardhatChangelog(nextHardhatAlphaVersion, changesets) {
142+
const newChangelogSection = generateChangelogFrom(
143+
nextHardhatAlphaVersion,
144+
changesets
145+
);
146+
147+
const hardhatChangelogPath = path.join(
148+
packagesDir,
149+
"hardhat",
150+
"CHANGELOG.md"
151+
);
152+
153+
const currentChangelog = await readFile(hardhatChangelogPath, "utf-8");
154+
155+
const newChangelog = currentChangelog.replace(
156+
"# hardhat\n",
157+
newChangelogSection
158+
);
159+
160+
await writeFile(hardhatChangelogPath, newChangelog);
161+
}
162+
163+
async function readAllPackageNames() {
164+
const ignoredChangesetPackages = JSON.parse(
165+
await readFile(path.join(changesetDir, "config.json"))
166+
).ignore;
167+
168+
const subdirs = await readdir(packagesDir);
169+
170+
const packageNames = [];
171+
172+
for (const dir of subdirs) {
173+
const packageJsonPath = path.join(packagesDir, dir, "package.json");
174+
175+
try {
176+
const stats = await stat(packageJsonPath);
177+
178+
if (!stats.isFile()) {
179+
continue;
180+
}
181+
182+
const pkgJson = JSON.parse(await readFile(packageJsonPath, "utf8"));
183+
184+
if (ignoredChangesetPackages.includes(pkgJson.name)) {
185+
continue;
186+
}
187+
188+
packageNames.push(pkgJson.name);
189+
} catch (error) {
190+
console.log(error);
191+
process.exit(1);
192+
}
193+
}
194+
195+
return packageNames.sort();
196+
}
197+
198+
function generateChangelogFrom(nextHardhatAlphaVersion, changesets) {
199+
const hardhatAlphaChangelog = `# hardhat
200+
201+
## ${nextHardhatAlphaVersion}
202+
203+
### Patch Changes
204+
205+
${changesets
206+
.map(({ content, commitHash }) =>
207+
content
208+
.trim()
209+
.split("\n")
210+
.map(
211+
(entry) =>
212+
`- ${
213+
commitHash !== null ? `${commitHash.slice(0, 7)}: ` : ""
214+
}${entry}`
215+
)
216+
.join("\n")
217+
)
218+
.join("\n")}
219+
`;
220+
221+
return hardhatAlphaChangelog;
222+
}
223+
224+
function parseFrontMatter(markdown) {
225+
const match = markdown.match(/^---\n([\s\S]*?)\n---\n([\s\S]*)$/);
226+
if (!match) {
227+
return { frontMatter: null, content: markdown };
228+
}
229+
230+
return {
231+
frontMatter: match[1],
232+
content: match[2],
233+
};
234+
}
235+
236+
async function getAddingCommit(filePath) {
237+
try {
238+
const { stdout } = await execAsync(
239+
`git log --diff-filter=A --follow --format=%h -- "${filePath}"`
240+
);
241+
return stdout.trim() || null;
242+
} catch {
243+
return null;
244+
}
245+
}
246+
247+
await main();

0 commit comments

Comments
 (0)