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
		
	
			
		
		
	
	
			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 |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();
 |