diff --git a/package.json b/package.json index d362729a8..c2372f385 100644 --- a/package.json +++ b/package.json @@ -12,7 +12,8 @@ "lucide-vue": "yarn workspace lucide-vue", "build:icons": "babel-node ./scripts/buildIcons.js --presets @babel/env", "optimize": "babel-node ./scripts/optimizeSvgs.js --presets @babel/env", - "addtags": "babel-node ./scripts/addMissingKeysToTags.js --presets @babel/env" + "addtags": "babel-node ./scripts/addMissingKeysToTags.js --presets @babel/env", + "generate:changelog": "babel-node ./scripts/generateChangelog.js --presets @babel/env" }, "devDependencies": { "@ampproject/rollup-plugin-closure-compiler": "^0.25.2", @@ -38,6 +39,7 @@ "jest": "^26.4.2", "lint-staged": "^10.5.3", "minimist": "^1.2.5", + "node-fetch": "^2.6.1", "prettier": "1.17.1", "rollup": "^2.7.3", "rollup-plugin-license": "^2.0.0", diff --git a/scripts/generateChangelog.js b/scripts/generateChangelog.js new file mode 100644 index 000000000..97dbc1f94 --- /dev/null +++ b/scripts/generateChangelog.js @@ -0,0 +1,76 @@ +import githubApi from './githubApi'; + +const fetchCompareTags = oldTag => + githubApi(`https://api.github.com/repos/lucide-icons/lucide/compare/${oldTag}...master`); + +const iconRegex = /icons\/(.*)\.svg/g; +const iconTemplate = ({ name, pullNumber, author }) => + `- \`${name}\` (${pullNumber}) by @${author}`; + +const topics = [ + { + title: 'New icons 🎨', + template: iconTemplate, + filter: ({ status, filename }) => status === 'added' && filename.match(iconRegex), + }, + { + title: 'Modified Icons 🔨', + template: iconTemplate, + filter: ({ status, filename }) => status === 'modified' && filename.match(iconRegex), + }, + { + title: 'Code improvements ⚡', + template: ({ title, pullNumber, author }) => `- ${title} (${pullNumber}) by @${author}`, + filter: ({ filename }, index, self) => + !filename.match(iconRegex) && self.indexOf(filename) === index, + }, +]; + +const fetchCommits = async file => { + const commits = await githubApi( + `https://api.github.com/repos/lucide-icons/lucide/commits?path=${file.filename}`, + ); + + return { ...file, commits }; +}; + +// eslint-disable-next-line func-names +(async function() { + const output = await fetchCompareTags('v0.13.0'); + const changedFiles = output.files.filter( + ({ filename }) => !filename.match(/site\/(.*)|(.*)package\.json|tags.json/g), + ); + + const commits = await Promise.all(changedFiles.map(fetchCommits)); + + const mappedCommits = commits + .map(({ commits: [pr], filename, sha, status }) => { + const pullNumber = /(.*)\((#[0-9]*)\)/gm.exec(pr.commit.message); + const nameRegex = /^\/?(.+\/)*(.+)\.(.+)$/g.exec(filename); + + return { + filename, + name: nameRegex && nameRegex[2] ? nameRegex[2] : null, + title: pullNumber && pullNumber[1] ? pullNumber[1].trim() : null, + pullNumber: pullNumber && pullNumber[2] ? pullNumber[2].trim() : null, + author: pr.author.login, + sha, + status, + }; + }) + .filter(({ pullNumber }) => !!pullNumber); + + const changelog = topics.map(({ title, filter, template }) => { + const lines = mappedCommits.filter(filter).map(template); + + if (lines.length) { + return [`## ${title}`, ' ', ...lines, ' ']; + } + + return ['']; + }); + + const changelogMarkown = changelog.flat().join('\n'); + + console.log(changelogMarkown); +})(); diff --git a/scripts/githubApi.js b/scripts/githubApi.js new file mode 100644 index 000000000..de5c5584a --- /dev/null +++ b/scripts/githubApi.js @@ -0,0 +1,22 @@ +// eslint-disable-next-line import/no-extraneous-dependencies +import fetch, { Headers } from 'node-fetch'; + +const githubApi = async endpoint => { + const headers = new Headers(); + const username = 'ericfennis'; + const password = process.env.GITHUB_API_KEY; + + headers.set( + 'Authorization', + `Basic ${Buffer.from(`${username}:${password}`).toString('base64')}`, + ); + + const res = await fetch(endpoint, { + method: 'GET', + headers, + }); + + return res.json(); +}; + +export default githubApi;