Skip to content

Commit e8a39d7

Browse files
committed
chore: workspace
1 parent 5c21e53 commit e8a39d7

3 files changed

Lines changed: 119 additions & 1 deletion

File tree

CHANGELOG.md

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,3 @@
1+
# Changelog
2+
3+
Combined release notes for the monorepo. Internal @repo packages are excluded from this log.

package.json

Lines changed: 3 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -11,7 +11,9 @@
1111
"lint": "turbo lint",
1212
"format": "turbo format",
1313
"changeset": "changeset",
14-
"version": "changeset version",
14+
"release:notes": "node scripts/update-root-changelog.mjs",
15+
"changelog:root": "node scripts/update-root-changelog.mjs --write",
16+
"version": "pnpm changelog:root && changeset version",
1517
"release": "changeset publish"
1618
},
1719
"devDependencies": {

scripts/update-root-changelog.mjs

Lines changed: 113 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,113 @@
1+
import { existsSync, readdirSync, readFileSync, writeFileSync } from "node:fs";
2+
import { join } from "node:path";
3+
4+
const root = process.cwd();
5+
const changesetDir = join(root, ".changeset");
6+
const changelogPath = join(root, "CHANGELOG.md");
7+
const args = new Set(process.argv.slice(2));
8+
const shouldWrite = args.has("--write");
9+
10+
if (!existsSync(changesetDir)) {
11+
process.exit(0);
12+
}
13+
14+
const changesetFiles = readdirSync(changesetDir)
15+
.filter((file) => file.endsWith(".md") && file !== "README.md")
16+
.sort();
17+
18+
if (changesetFiles.length === 0) {
19+
process.exit(0);
20+
}
21+
22+
const parseChangeset = (fileName) => {
23+
const content = readFileSync(join(changesetDir, fileName), "utf8");
24+
const match = content.match(/^---\n([\s\S]*?)\n---\n([\s\S]*)$/);
25+
26+
if (!match) {
27+
return { packages: [], note: "" };
28+
}
29+
30+
const frontmatter = match[1];
31+
const note = match[2].trim();
32+
const packages = [];
33+
34+
for (const line of frontmatter.split("\n")) {
35+
const packageMatch = line.match(/^'([^']+)':\s*(patch|minor|major)\s*$/);
36+
if (packageMatch) {
37+
const packageName = packageMatch[1];
38+
if (!packageName.startsWith("@repo/")) {
39+
packages.push({ name: packageName, bump: packageMatch[2] });
40+
}
41+
}
42+
}
43+
44+
return { packages, note };
45+
};
46+
47+
const parsed = changesetFiles.map(parseChangeset);
48+
const packageBumps = parsed.flatMap((entry) => entry.packages);
49+
50+
if (packageBumps.length === 0) {
51+
process.exit(0);
52+
}
53+
54+
const seen = new Set();
55+
const uniquePackageBumps = [];
56+
for (const bump of packageBumps) {
57+
const key = `${bump.name}:${bump.bump}`;
58+
if (!seen.has(key)) {
59+
seen.add(key);
60+
uniquePackageBumps.push(bump);
61+
}
62+
}
63+
64+
const notes = parsed
65+
.map((entry) => entry.note)
66+
.filter(Boolean)
67+
.map((note) => note.replace(/\s+/g, " "));
68+
69+
const marker = `<!-- changesets: ${changesetFiles.join(",")} -->`;
70+
const date = new Date().toISOString().slice(0, 10);
71+
72+
const section = [
73+
marker,
74+
`## ${date}`,
75+
"",
76+
"### Highlights",
77+
...(notes.length > 0 ? notes.map((note) => `- ${note}`) : ["- No additional notes."]),
78+
"",
79+
"### Package Releases",
80+
...uniquePackageBumps.map((entry) => `- ${entry.name}: ${entry.bump}`),
81+
"",
82+
"### Full Changelog",
83+
"Package | Release type",
84+
"--- | ---",
85+
...uniquePackageBumps.map((entry) => `${entry.name} | ${entry.bump}`),
86+
"",
87+
].join("\n");
88+
89+
const header = "# Changelog\n\nCombined release notes for the monorepo. Internal @repo packages are excluded from this log.\n\n";
90+
91+
const template = `<!--\nDOTENV-DIFF RELEASE NOTES TEMPLATE\nGenerated from pending changesets. Internal @repo packages are excluded.\n-->\n\n${section}\n<!-- END TEMPLATE -->\n`;
92+
93+
if (!shouldWrite) {
94+
console.log(template);
95+
process.exit(0);
96+
}
97+
98+
if (!existsSync(changelogPath)) {
99+
writeFileSync(changelogPath, `${header}${section}`, "utf8");
100+
process.exit(0);
101+
}
102+
103+
const existing = readFileSync(changelogPath, "utf8");
104+
if (existing.includes(marker)) {
105+
process.exit(0);
106+
}
107+
108+
if (existing.startsWith("# Changelog")) {
109+
const updated = existing.replace(/^# Changelog\n\n/, `# Changelog\n\n${section}`);
110+
writeFileSync(changelogPath, updated, "utf8");
111+
} else {
112+
writeFileSync(changelogPath, `${header}${section}${existing}`, "utf8");
113+
}

0 commit comments

Comments
 (0)