Skip to content

Commit f6ef097

Browse files
committed
Improve changelog format
1 parent bb87e26 commit f6ef097

13 files changed

Lines changed: 457 additions & 23 deletions

File tree

.changeset/changelog-formatter.js

Lines changed: 92 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,92 @@
1+
const { getInfo } = require("@changesets/get-github-info");
2+
3+
/**
4+
* Custom changelog formatter for Plot project
5+
* Follows "Keep a Changelog" standard with categories:
6+
* Added, Changed, Deprecated, Removed, Fixed, Security
7+
*
8+
* Expects changeset summaries to be prefixed with category, e.g.:
9+
* "Added: new feature"
10+
* "Fixed: bug description"
11+
*/
12+
13+
const CATEGORIES = [
14+
"Added",
15+
"Changed",
16+
"Deprecated",
17+
"Removed",
18+
"Fixed",
19+
"Security"
20+
];
21+
22+
const CATEGORY_PATTERN = new RegExp(`^(${CATEGORIES.join("|")}):\\s*`, "i");
23+
24+
/**
25+
* Parse a changeset summary to extract category and content
26+
*/
27+
function parseChangeset(summary) {
28+
const match = summary.match(CATEGORY_PATTERN);
29+
30+
if (match) {
31+
const category = match[1].charAt(0).toUpperCase() + match[1].slice(1).toLowerCase();
32+
const content = summary.slice(match[0].length);
33+
return { category, content };
34+
}
35+
36+
// Default to "Changed" if no category prefix found
37+
return { category: "Changed", content: summary };
38+
}
39+
40+
/**
41+
* Format a single release line for a changeset
42+
*/
43+
async function getReleaseLine(changeset, type, options) {
44+
if (!options || !options.repo) {
45+
throw new Error("Must provide options.repo for changelog-formatter");
46+
}
47+
48+
const { summary } = changeset;
49+
const { category, content } = parseChangeset(summary);
50+
51+
let links = "";
52+
53+
try {
54+
const info = await getInfo({
55+
repo: options.repo,
56+
commit: changeset.commit,
57+
});
58+
59+
// Format: [#PR](link) [`hash`](link)
60+
const prLink = info.pull ? `[#${info.pull}](${info.links.pull})` : null;
61+
const commitLink = info.commit ? `[\`${info.commit.slice(0, 7)}\`](${info.links.commit})` : null;
62+
63+
const linkParts = [prLink, commitLink].filter(Boolean);
64+
if (linkParts.length > 0) {
65+
links = ` (${linkParts.join(" ")})`;
66+
}
67+
} catch (error) {
68+
// If we can't get GitHub info, just continue without links
69+
console.warn("Could not get GitHub info for changeset:", error.message);
70+
}
71+
72+
// Include category as HTML comment for post-processing
73+
return `<!-- CATEGORY:${category} -->${content}${links}`;
74+
}
75+
76+
/**
77+
* Format dependency update lines
78+
*/
79+
async function getDependencyReleaseLine(changesets, dependenciesUpdated, options) {
80+
if (changesets.length === 0) return "";
81+
82+
const updatedDependencies = dependenciesUpdated.map(
83+
(dependency) => ` - ${dependency.name}@${dependency.newVersion}`
84+
);
85+
86+
return ["<!-- CATEGORY:Changed -->Updated dependencies:", ...updatedDependencies].join("\n");
87+
}
88+
89+
module.exports = {
90+
getReleaseLine,
91+
getDependencyReleaseLine,
92+
};

.changeset/config.json

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,6 @@
11
{
22
"$schema": "https://unpkg.com/@changesets/config@3.1.1/schema.json",
3-
"changelog": ["@changesets/changelog-github", { "repo": "plotday/plot" }],
3+
"changelog": ["./.changeset/changelog-formatter.js", { "repo": "plotday/plot" }],
44
"commit": false,
55
"fixed": [],
66
"linked": [],

.changeset/fruity-horses-grow.md

Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,8 @@
1+
---
2+
"@plotday/sdk": patch
3+
"@plotday/tool-google-calendar": patch
4+
"@plotday/tool-google-contacts": patch
5+
"@plotday/tool-outlook-calendar": patch
6+
---
7+
8+
Changed: improved changelog format
Lines changed: 160 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,160 @@
1+
#!/usr/bin/env node
2+
3+
/**
4+
* Post-processing script to reorganize CHANGELOG.md files
5+
* Converts changesets' default grouping (Major/Minor/Patch Changes)
6+
* into "Keep a Changelog" format (Added/Changed/Fixed/etc.)
7+
*/
8+
9+
const fs = require("fs");
10+
const path = require("path");
11+
12+
const CATEGORIES = [
13+
"Added",
14+
"Changed",
15+
"Deprecated",
16+
"Removed",
17+
"Fixed",
18+
"Security"
19+
];
20+
21+
/**
22+
* Process a single CHANGELOG.md file
23+
*/
24+
function processChangelog(filePath) {
25+
const content = fs.readFileSync(filePath, "utf-8");
26+
const lines = content.split("\n");
27+
28+
let result = [];
29+
let i = 0;
30+
31+
while (i < lines.length) {
32+
const line = lines[i];
33+
34+
// Check if this is a version header (e.g., "## 1.0.0")
35+
if (line.match(/^##\s+\d+\.\d+\.\d+/)) {
36+
result.push(line);
37+
i++;
38+
39+
// Skip empty lines after version header
40+
while (i < lines.length && lines[i].trim() === "") {
41+
i++;
42+
}
43+
44+
// Check if we have change type sections (Minor Changes, Patch Changes, etc.)
45+
const sectionStart = i;
46+
const changes = [];
47+
48+
// Collect all changes in this version
49+
let currentCategory = null;
50+
51+
while (i < lines.length && !lines[i].match(/^##\s+\d+\.\d+\.\d+/)) {
52+
const currentLine = lines[i];
53+
54+
// Check for "Keep a Changelog" category headers (already formatted)
55+
const keepAChangelogMatch = currentLine.match(/^###\s+(Added|Changed|Deprecated|Removed|Fixed|Security)$/);
56+
if (keepAChangelogMatch) {
57+
currentCategory = keepAChangelogMatch[1];
58+
i++;
59+
continue;
60+
}
61+
62+
// Skip the old "### Minor Changes", "### Patch Changes" headers
63+
if (currentLine.match(/^###\s+(Major|Minor|Patch)\s+Changes/)) {
64+
currentCategory = null; // Reset category for old format
65+
i++;
66+
continue;
67+
}
68+
69+
// Check for category markers in the content (from changelog formatter)
70+
const categoryMatch = currentLine.match(/<!--\s*CATEGORY:(\w+)\s*-->(.*)/);
71+
if (categoryMatch) {
72+
const category = categoryMatch[1];
73+
const content = categoryMatch[2];
74+
changes.push({ category, content: `- ${content}` });
75+
i++;
76+
} else if (currentLine.trim() !== "" && currentLine.match(/^-\s+/)) {
77+
// If we have a current category (from Keep a Changelog format), use it
78+
// Otherwise, treat as "Changed" (legacy/old format)
79+
const category = currentCategory || "Changed";
80+
const contentLines = [currentLine];
81+
i++;
82+
83+
// Collect any indented continuation lines (e.g., for dependency lists)
84+
while (i < lines.length && lines[i].match(/^ /)) {
85+
contentLines.push(lines[i]);
86+
i++;
87+
}
88+
89+
changes.push({ category, content: contentLines.join("\n") });
90+
} else {
91+
i++;
92+
}
93+
}
94+
95+
// Group changes by category and output in "Keep a Changelog" order
96+
if (changes.length > 0) {
97+
result.push(""); // Empty line after version header
98+
99+
for (const category of CATEGORIES) {
100+
const categoryChanges = changes.filter(c => c.category === category);
101+
if (categoryChanges.length > 0) {
102+
result.push(`### ${category}`);
103+
result.push("");
104+
categoryChanges.forEach(change => {
105+
result.push(change.content);
106+
});
107+
result.push("");
108+
}
109+
}
110+
}
111+
} else {
112+
// Keep non-version lines as-is (like the package name header)
113+
result.push(line);
114+
i++;
115+
}
116+
}
117+
118+
// Write the reorganized content back
119+
fs.writeFileSync(filePath, result.join("\n"));
120+
console.log(`Reorganized: ${filePath}`);
121+
}
122+
123+
/**
124+
* Recursively find CHANGELOG.md files
125+
*/
126+
function findChangelogFiles(dir, fileList = []) {
127+
const files = fs.readdirSync(dir);
128+
129+
files.forEach(file => {
130+
const filePath = path.join(dir, file);
131+
const stat = fs.statSync(filePath);
132+
133+
if (stat.isDirectory()) {
134+
// Skip node_modules and hidden directories
135+
if (file !== "node_modules" && !file.startsWith(".")) {
136+
findChangelogFiles(filePath, fileList);
137+
}
138+
} else if (file === "CHANGELOG.md") {
139+
fileList.push(filePath);
140+
}
141+
});
142+
143+
return fileList;
144+
}
145+
146+
/**
147+
* Find and process all CHANGELOG.md files
148+
*/
149+
function main() {
150+
const rootDir = path.join(__dirname, "..");
151+
const changelogFiles = findChangelogFiles(rootDir);
152+
153+
console.log(`Found ${changelogFiles.length} changelog files`);
154+
155+
changelogFiles.forEach(processChangelog);
156+
157+
console.log("Done reorganizing changelogs!");
158+
}
159+
160+
main();

0 commit comments

Comments
 (0)