You cannot select more than 25 topics Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.

136 lines
3.9 KiB
TypeScript

import { consola } from 'consola';
import { readJsonSync, writeJSONSync } from 'fs-extra';
import { markdownToTxt } from 'markdown-to-txt';
import { existsSync, readFileSync } from 'node:fs';
import { resolve } from 'node:path';
import semver from 'semver';
import { CHANGELOG_DIR, CHANGELOG_FILE } from './const';
export interface ChangelogStaticItem {
children: {
[category: string]: string[];
};
date: string;
version: string;
}
class BuildStaticChangelog {
private removeDetailsTag = (changelog: string): string => {
const detailsRegex: RegExp = /<details\b[^>]*>[\S\s]*?<\/details>/gi;
return changelog.replaceAll(detailsRegex, '');
};
private cleanVersion = (version: string): string => {
return semver.clean(version) || version;
};
private formatCategory = (category: string): string => {
const cate = category.trim().toLowerCase();
switch (cate) {
case 'bug fixes': {
return 'fixes';
}
case 'features': {
return 'features';
}
default: {
return 'improvements';
}
}
};
private formatChangelog = (changelog: string): ChangelogStaticItem[] => {
const cleanedChangelog = this.removeDetailsTag(changelog);
const input = markdownToTxt(cleanedChangelog);
const versions = input.split(/Version&nbsp;|Version /).slice(1);
const output: ChangelogStaticItem[] = [];
for (const version of versions) {
const lines = version.trim().split('\n');
const versionNumber = lines[0].trim();
const date = lines[2].replace('Released on ', '').trim();
const entry: ChangelogStaticItem = {
children: {},
date: date,
version: this.cleanVersion(versionNumber),
};
let currentCategory = '';
let skipSection = false;
for (let i = 3; i < lines.length; i++) {
const line = lines[i].trim();
if (line === '') continue;
if (/^\p{Emoji}/u.test(line)) {
currentCategory = this.formatCategory(line.replace(/^\p{Emoji} /u, ''));
if (!currentCategory) continue;
entry.children[currentCategory] = [];
skipSection = false;
} else if (line.startsWith('misc:') && !skipSection && currentCategory) {
entry.children[currentCategory].push(line.replace('misc:', '').trim());
}
}
// Remove empty categories
for (const category in entry.children) {
if (entry.children[category].length === 0) {
delete entry.children[category];
}
}
output.push(entry);
}
return output;
};
private mergeAndSortVersions = (oldVersions: any, newVersions: any) => {
const mergedVersions = [...oldVersions];
for (const newVersion of newVersions) {
const existingIndex = mergedVersions.findIndex(
(v) => this.cleanVersion(v.version) === this.cleanVersion(newVersion.version),
);
if (existingIndex === -1) {
const insertIndex = mergedVersions.findIndex(
(v) =>
semver.compare(this.cleanVersion(newVersion.version), this.cleanVersion(v.version)) > 0,
);
if (insertIndex === -1) {
mergedVersions.push(newVersion);
} else {
mergedVersions.splice(insertIndex, 0, newVersion);
}
}
}
return mergedVersions;
};
run() {
Object.entries(CHANGELOG_FILE).forEach(([version, path]) => {
const data = readFileSync(path, 'utf8');
const newFile = this.formatChangelog(data);
const filename = resolve(CHANGELOG_DIR, `${version}.json`);
let mergedFile = newFile;
if (existsSync(filename)) {
const oldFile = readJsonSync(filename, 'utf8');
mergedFile = this.mergeAndSortVersions(oldFile, newFile);
}
writeJSONSync(filename, mergedFile, { spaces: 2 });
consola.success(`Changelog ${version} has been built successfully!`);
});
}
}
export const buildStaticChangelog = new BuildStaticChangelog();