keepachangelog-fmt 0.1.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +23 -0
- package/dist/index.d.mts +1 -0
- package/dist/index.mjs +154 -0
- package/package.json +33 -0
package/README.md
ADDED
|
@@ -0,0 +1,23 @@
|
|
|
1
|
+
# tsdown-starter
|
|
2
|
+
|
|
3
|
+
A starter for creating a TypeScript package.
|
|
4
|
+
|
|
5
|
+
## Development
|
|
6
|
+
|
|
7
|
+
- Install dependencies:
|
|
8
|
+
|
|
9
|
+
```bash
|
|
10
|
+
npm install
|
|
11
|
+
```
|
|
12
|
+
|
|
13
|
+
- Run the unit tests:
|
|
14
|
+
|
|
15
|
+
```bash
|
|
16
|
+
npm run test
|
|
17
|
+
```
|
|
18
|
+
|
|
19
|
+
- Build the library:
|
|
20
|
+
|
|
21
|
+
```bash
|
|
22
|
+
npm run build
|
|
23
|
+
```
|
package/dist/index.d.mts
ADDED
|
@@ -0,0 +1 @@
|
|
|
1
|
+
export { };
|
package/dist/index.mjs
ADDED
|
@@ -0,0 +1,154 @@
|
|
|
1
|
+
import fs from "node:fs/promises";
|
|
2
|
+
//#region src/constants.ts
|
|
3
|
+
const RELEASE_HEADING_REGEX = /^## \[(.+)\](?=(?: - (\d{4}-\d{2}-\d{2})?\s?(?:\((.+)\))?)?$)/;
|
|
4
|
+
//#endregion
|
|
5
|
+
//#region src/parse.ts
|
|
6
|
+
const SECTION_TITLES = [
|
|
7
|
+
"Added",
|
|
8
|
+
"Changed",
|
|
9
|
+
"Removed",
|
|
10
|
+
"Fixed"
|
|
11
|
+
];
|
|
12
|
+
const isTitleHeading = (line) => line.startsWith("# ");
|
|
13
|
+
const isReleaseHeading = (line) => line.startsWith("## [");
|
|
14
|
+
const isSectionHeading = (line) => line.startsWith("### ") && SECTION_TITLES.includes(line.replace("### ", ""));
|
|
15
|
+
const isEmptyLine = (line) => line === "";
|
|
16
|
+
const isValidPreambleLine = (line) => !isTitleHeading(line) && !isReleaseHeading(line) && !isSectionHeading(line);
|
|
17
|
+
const isBullet = (line) => line.startsWith("- ");
|
|
18
|
+
const removeTrailingNewLines = (lines) => {
|
|
19
|
+
let newArray = [...lines];
|
|
20
|
+
while (isEmptyLine(newArray[0])) newArray = newArray.slice(1);
|
|
21
|
+
while (isEmptyLine(newArray[newArray.length - 1])) newArray = newArray.slice(0, -1);
|
|
22
|
+
return newArray;
|
|
23
|
+
};
|
|
24
|
+
function parse(text) {
|
|
25
|
+
const changelog = {
|
|
26
|
+
type: "changelog",
|
|
27
|
+
title: "# Changelog",
|
|
28
|
+
preamble: [],
|
|
29
|
+
releases: []
|
|
30
|
+
};
|
|
31
|
+
const lines = text.split("\n");
|
|
32
|
+
let lineNumber = 0;
|
|
33
|
+
const parseTitle = () => {
|
|
34
|
+
const titleLine = lines[lineNumber];
|
|
35
|
+
if (isTitleHeading(titleLine)) {
|
|
36
|
+
changelog.title = titleLine;
|
|
37
|
+
lineNumber++;
|
|
38
|
+
}
|
|
39
|
+
};
|
|
40
|
+
const parsePreamble = () => {
|
|
41
|
+
while (lineNumber < lines.length) {
|
|
42
|
+
const line = lines[lineNumber];
|
|
43
|
+
if (!isValidPreambleLine(line)) break;
|
|
44
|
+
changelog.preamble.push(line);
|
|
45
|
+
lineNumber++;
|
|
46
|
+
}
|
|
47
|
+
changelog.preamble = removeTrailingNewLines(changelog.preamble);
|
|
48
|
+
};
|
|
49
|
+
const parseChanges = () => {
|
|
50
|
+
const changes = [];
|
|
51
|
+
while (lineNumber < lines.length) {
|
|
52
|
+
const line = lines[lineNumber];
|
|
53
|
+
if (isEmptyLine(line)) {
|
|
54
|
+
lineNumber++;
|
|
55
|
+
continue;
|
|
56
|
+
}
|
|
57
|
+
if (!isBullet(line)) break;
|
|
58
|
+
changes.push({
|
|
59
|
+
type: "change",
|
|
60
|
+
text: [line.replace("- ", "")]
|
|
61
|
+
});
|
|
62
|
+
lineNumber++;
|
|
63
|
+
}
|
|
64
|
+
return changes;
|
|
65
|
+
};
|
|
66
|
+
const parseSection = () => {
|
|
67
|
+
const title = lines[lineNumber].replace("### ", "");
|
|
68
|
+
lineNumber++;
|
|
69
|
+
return {
|
|
70
|
+
type: "section",
|
|
71
|
+
title,
|
|
72
|
+
changes: parseChanges()
|
|
73
|
+
};
|
|
74
|
+
};
|
|
75
|
+
const parseSections = () => {
|
|
76
|
+
const sections = [];
|
|
77
|
+
while (lineNumber < lines.length) {
|
|
78
|
+
const line = lines[lineNumber];
|
|
79
|
+
if (isEmptyLine(line)) {
|
|
80
|
+
lineNumber++;
|
|
81
|
+
continue;
|
|
82
|
+
}
|
|
83
|
+
if (!isSectionHeading(line)) break;
|
|
84
|
+
sections.push(parseSection());
|
|
85
|
+
}
|
|
86
|
+
return sections;
|
|
87
|
+
};
|
|
88
|
+
const parseRelease = () => {
|
|
89
|
+
const line = lines[lineNumber];
|
|
90
|
+
const matches = RELEASE_HEADING_REGEX.exec(line.trim());
|
|
91
|
+
if (!matches) throw new Error(`Invalid release heading on line ${lineNumber}: "${line}"`);
|
|
92
|
+
const [_, version, date, label] = matches;
|
|
93
|
+
lineNumber++;
|
|
94
|
+
const release = {
|
|
95
|
+
type: "release",
|
|
96
|
+
version,
|
|
97
|
+
date,
|
|
98
|
+
label,
|
|
99
|
+
sections: parseSections()
|
|
100
|
+
};
|
|
101
|
+
changelog.releases.push(release);
|
|
102
|
+
};
|
|
103
|
+
const parseReleases = () => {
|
|
104
|
+
while (lineNumber < lines.length) {
|
|
105
|
+
const line = lines[lineNumber];
|
|
106
|
+
if (isEmptyLine(line)) {
|
|
107
|
+
lineNumber++;
|
|
108
|
+
continue;
|
|
109
|
+
}
|
|
110
|
+
if (!isReleaseHeading(line)) {
|
|
111
|
+
lineNumber++;
|
|
112
|
+
break;
|
|
113
|
+
}
|
|
114
|
+
parseRelease();
|
|
115
|
+
}
|
|
116
|
+
};
|
|
117
|
+
parseTitle();
|
|
118
|
+
parsePreamble();
|
|
119
|
+
parseReleases();
|
|
120
|
+
return changelog;
|
|
121
|
+
}
|
|
122
|
+
//#endregion
|
|
123
|
+
//#region src/format.ts
|
|
124
|
+
function format(changelog) {
|
|
125
|
+
const preambleText = changelog.preamble.length !== 0 ? `\n\n${changelog.preamble.join("\n")}\n` : "";
|
|
126
|
+
const releasesText = changelog.releases.map((release) => {
|
|
127
|
+
let title = `## [${release.version.trim()}]`;
|
|
128
|
+
if (release.date || release.label) {
|
|
129
|
+
title += " -";
|
|
130
|
+
if (release.date) title += ` ${release.date}`;
|
|
131
|
+
if (release.label) title += ` (${release.label.trim()})`;
|
|
132
|
+
}
|
|
133
|
+
const sections = release.sections.map((section) => {
|
|
134
|
+
const title = `### ${section.title.trim()}`;
|
|
135
|
+
const changes = section.changes.map((change) => {
|
|
136
|
+
const line = change.text[0].trim();
|
|
137
|
+
return `- ${line}${line[line.length - 1] === "." ? "" : "."}`;
|
|
138
|
+
}).join("\n");
|
|
139
|
+
if (changes === "") return title;
|
|
140
|
+
return `${title}\n\n${changes}`;
|
|
141
|
+
}).join("\n\n");
|
|
142
|
+
return `${title}\n\n${sections}`;
|
|
143
|
+
}).join("\n\n\n");
|
|
144
|
+
return `${changelog.title}${preambleText}\n\n${releasesText}\n`;
|
|
145
|
+
}
|
|
146
|
+
//#endregion
|
|
147
|
+
//#region src/index.ts
|
|
148
|
+
async function main() {
|
|
149
|
+
const formatted = format(parse(await fs.readFile("CHANGELOG.md", { encoding: "utf8" })));
|
|
150
|
+
await fs.writeFile("CHANGELOG.md", formatted, { encoding: "utf8" });
|
|
151
|
+
}
|
|
152
|
+
main();
|
|
153
|
+
//#endregion
|
|
154
|
+
export {};
|
package/package.json
ADDED
|
@@ -0,0 +1,33 @@
|
|
|
1
|
+
{
|
|
2
|
+
"name": "keepachangelog-fmt",
|
|
3
|
+
"type": "module",
|
|
4
|
+
"version": "0.1.0",
|
|
5
|
+
"description": "A formatter for changelogs following the \"keep a changelog\" format.",
|
|
6
|
+
"author": "Bram <bram@brams.dev>",
|
|
7
|
+
"license": "MIT",
|
|
8
|
+
"exports": {
|
|
9
|
+
".": "./dist/index.mjs",
|
|
10
|
+
"./package.json": "./package.json"
|
|
11
|
+
},
|
|
12
|
+
"files": [
|
|
13
|
+
"dist"
|
|
14
|
+
],
|
|
15
|
+
"scripts": {
|
|
16
|
+
"build": "tsdown",
|
|
17
|
+
"dev": "tsdown --watch",
|
|
18
|
+
"test": "vitest",
|
|
19
|
+
"typecheck": "tsc --noEmit",
|
|
20
|
+
"release": "bumpp",
|
|
21
|
+
"prepublishOnly": "pnpm run build",
|
|
22
|
+
"publish": "npm publish",
|
|
23
|
+
"format": "bun run src/index.ts"
|
|
24
|
+
},
|
|
25
|
+
"devDependencies": {
|
|
26
|
+
"@types/node": "^25.5.0",
|
|
27
|
+
"@typescript/native-preview": "7.0.0-dev.20260328.1",
|
|
28
|
+
"bumpp": "^11.0.1",
|
|
29
|
+
"tsdown": "^0.21.7",
|
|
30
|
+
"typescript": "^6.0.2",
|
|
31
|
+
"vitest": "^4.1.3"
|
|
32
|
+
}
|
|
33
|
+
}
|