mirror of
https://github.com/haveno-dex/haveno-ui.git
synced 2024-12-12 09:34:30 -05:00
a9893aa853
Electron, React, Vite app boilerplate - license header - pre-commit and commit-msg hooks - storybook - fix windows tests; - fix linux build - CI setup - persistent store with electron-store and safeStorage - localization with react-intl Refs: - https://github.com/haveno-dex/haveno-ui/projects/1#card-81001746 - https://github.com/haveno-dex/haveno-ui/projects/1#card-81001745 Authored-by: schowdhuri Reviewed-by: localredhead
343 lines
7.6 KiB
JavaScript
343 lines
7.6 KiB
JavaScript
// TODO: Refactor this action
|
|
|
|
const { execSync } = require("child_process");
|
|
|
|
/**
|
|
* Gets the value of an input. The value is also trimmed.
|
|
*
|
|
* @param name name of the input to get
|
|
* @param options optional. See InputOptions.
|
|
* @returns string
|
|
*/
|
|
function getInput(name, options) {
|
|
const val =
|
|
process.env[`INPUT_${name.replace(/ /g, "_").toUpperCase()}`] || "";
|
|
if (options && options.required && !val) {
|
|
throw new Error(`Input required and not supplied: ${name}`);
|
|
}
|
|
|
|
return val.trim();
|
|
}
|
|
|
|
const START_FROM = getInput("from");
|
|
const END_TO = getInput("to");
|
|
const INCLUDE_COMMIT_BODY = getInput("include-commit-body") === "true";
|
|
const INCLUDE_ABBREVIATED_COMMIT =
|
|
getInput("include-abbreviated-commit") === "true";
|
|
|
|
/**
|
|
* @typedef {Object} ICommit
|
|
* @property {string | undefined} abbreviated_commit
|
|
* @property {string | undefined} subject
|
|
* @property {string | undefined} body
|
|
*/
|
|
|
|
/**
|
|
* @typedef {ICommit & {type: string | undefined, scope: string | undefined}} ICommitExtended
|
|
*/
|
|
|
|
/**
|
|
* Any unique string that is guaranteed not to be used in committee text.
|
|
* Used to split data in the commit line
|
|
* @type {string}
|
|
*/
|
|
const commitInnerSeparator = "~~~~";
|
|
|
|
/**
|
|
* Any unique string that is guaranteed not to be used in committee text.
|
|
* Used to split each commit line
|
|
* @type {string}
|
|
*/
|
|
const commitOuterSeparator = "₴₴₴₴";
|
|
|
|
/**
|
|
* Commit data to be obtained.
|
|
* @type {Map<string, string>}
|
|
*
|
|
* @see https://git-scm.com/docs/git-log#Documentation/git-log.txt-emnem
|
|
*/
|
|
const commitDataMap = new Map([
|
|
["subject", "%s"], // Required
|
|
]);
|
|
|
|
if (INCLUDE_COMMIT_BODY) {
|
|
commitDataMap.set("body", "%b");
|
|
}
|
|
|
|
if (INCLUDE_ABBREVIATED_COMMIT) {
|
|
commitDataMap.set("abbreviated_commit", "%h");
|
|
}
|
|
|
|
/**
|
|
* The type used to group commits that do not comply with the convention
|
|
* @type {string}
|
|
*/
|
|
const fallbackType = "other";
|
|
|
|
/**
|
|
* List of all desired commit groups and in what order to display them.
|
|
* @type {string[]}
|
|
*/
|
|
const supportedTypes = [
|
|
"feat",
|
|
"fix",
|
|
"perf",
|
|
"refactor",
|
|
"style",
|
|
"docs",
|
|
"test",
|
|
"build",
|
|
"ci",
|
|
"chore",
|
|
"revert",
|
|
"deps",
|
|
fallbackType,
|
|
];
|
|
|
|
/**
|
|
* @param {string} commitString
|
|
* @returns {ICommit}
|
|
*/
|
|
function parseCommit(commitString) {
|
|
/** @type {ICommit} */
|
|
const commitDataObj = {};
|
|
const commitDataArray = commitString
|
|
.split(commitInnerSeparator)
|
|
.map((s) => s.trim());
|
|
|
|
for (const [key] of commitDataMap) {
|
|
commitDataObj[key] = commitDataArray.shift();
|
|
}
|
|
|
|
return commitDataObj;
|
|
}
|
|
|
|
/**
|
|
* Returns an array of commits since the last git tag
|
|
* @return {ICommit[]}
|
|
*/
|
|
function getCommits() {
|
|
const format =
|
|
Array.from(commitDataMap.values()).join(commitInnerSeparator) +
|
|
commitOuterSeparator;
|
|
|
|
const logs = String(
|
|
execSync(
|
|
`git --no-pager log ${START_FROM}..${END_TO} --pretty=format:"${format}" --reverse`
|
|
)
|
|
);
|
|
|
|
return logs
|
|
.trim()
|
|
.split(commitOuterSeparator)
|
|
.filter((r) => !!r.trim()) // Skip empty lines
|
|
.map(parseCommit);
|
|
}
|
|
|
|
/**
|
|
*
|
|
* @param {ICommit} commit
|
|
* @return {ICommitExtended}
|
|
*/
|
|
function setCommitTypeAndScope(commit) {
|
|
const matchRE = new RegExp(
|
|
`^(?:(${supportedTypes.join("|")})(?:\\((\\S+)\\))?:)?(.*)`,
|
|
"i"
|
|
);
|
|
|
|
let [, type, scope, clearSubject] = commit.subject.match(matchRE);
|
|
|
|
/**
|
|
* Additional rules for checking committees that do not comply with the convention, but for which it is possible to determine the type.
|
|
*/
|
|
// Commits like `revert something`
|
|
if (type === undefined && commit.subject.startsWith("revert")) {
|
|
type = "revert";
|
|
}
|
|
|
|
return {
|
|
...commit,
|
|
type: (type || fallbackType).toLowerCase().trim(),
|
|
scope: (scope || "").toLowerCase().trim(),
|
|
subject: (clearSubject || commit.subject).trim(),
|
|
};
|
|
}
|
|
|
|
class CommitGroup {
|
|
constructor() {
|
|
this.scopes = new Map();
|
|
this.commits = [];
|
|
}
|
|
|
|
/**
|
|
*
|
|
* @param {ICommitExtended[]} array
|
|
* @param {ICommitExtended} commit
|
|
*/
|
|
static _pushOrMerge(array, commit) {
|
|
const similarCommit = array.find((c) => c.subject === commit.subject);
|
|
if (similarCommit) {
|
|
if (commit.abbreviated_commit !== undefined) {
|
|
similarCommit.abbreviated_commit += `, ${commit.abbreviated_commit}`;
|
|
}
|
|
} else {
|
|
array.push(commit);
|
|
}
|
|
}
|
|
|
|
/**
|
|
* @param {ICommitExtended} commit
|
|
*/
|
|
push(commit) {
|
|
if (!commit.scope) {
|
|
CommitGroup._pushOrMerge(this.commits, commit);
|
|
return;
|
|
}
|
|
|
|
const scope = this.scopes.get(commit.scope) || { commits: [] };
|
|
CommitGroup._pushOrMerge(scope.commits, commit);
|
|
this.scopes.set(commit.scope, scope);
|
|
}
|
|
|
|
get isEmpty() {
|
|
return this.commits.length === 0 && this.scopes.size === 0;
|
|
}
|
|
}
|
|
|
|
/**
|
|
* Groups all commits by type and scopes
|
|
* @param {ICommit[]} commits
|
|
* @returns {Map<string, CommitGroup>}
|
|
*/
|
|
function getGroupedCommits(commits) {
|
|
const parsedCommits = commits.map(setCommitTypeAndScope);
|
|
|
|
const types = new Map(supportedTypes.map((id) => [id, new CommitGroup()]));
|
|
|
|
for (const parsedCommit of parsedCommits) {
|
|
const typeId = parsedCommit.type;
|
|
const type = types.get(typeId);
|
|
type.push(parsedCommit);
|
|
}
|
|
|
|
return types;
|
|
}
|
|
|
|
/**
|
|
* Return markdown list with commits
|
|
* @param {ICommitExtended[]} commits
|
|
* @param {string} pad
|
|
* @returns {string}
|
|
*/
|
|
function getCommitsList(commits, pad = "") {
|
|
let changelog = "";
|
|
for (const commit of commits) {
|
|
changelog += `${pad}- ${commit.subject}.`;
|
|
|
|
if (commit.abbreviated_commit !== undefined) {
|
|
changelog += ` (${commit.abbreviated_commit})`;
|
|
}
|
|
|
|
changelog += "\r\n";
|
|
|
|
if (commit.body === undefined) {
|
|
continue;
|
|
}
|
|
|
|
const body = commit.body.replace("[skip ci]", "").trim();
|
|
if (body !== "") {
|
|
changelog += `${body
|
|
.split(/\r*\n+/)
|
|
.filter((s) => !!s.trim())
|
|
.map((s) => `${pad} ${s}`)
|
|
.join("\r\n")}${"\r\n"}`;
|
|
}
|
|
}
|
|
|
|
return changelog;
|
|
}
|
|
|
|
function replaceHeader(str) {
|
|
switch (str) {
|
|
case "feat":
|
|
return "New Features";
|
|
case "fix":
|
|
return "Bug Fixes";
|
|
case "docs":
|
|
return "Documentation Changes";
|
|
case "build":
|
|
return "Build System";
|
|
case "chore":
|
|
return "Chores";
|
|
case "ci":
|
|
return "Continuous Integration";
|
|
case "refactor":
|
|
return "Refactors";
|
|
case "style":
|
|
return "Code Style Changes";
|
|
case "test":
|
|
return "Tests";
|
|
case "perf":
|
|
return "Performance improvements";
|
|
case "revert":
|
|
return "Reverts";
|
|
case "deps":
|
|
return "Dependency updates";
|
|
case "other":
|
|
return "Other Changes";
|
|
default:
|
|
return str;
|
|
}
|
|
}
|
|
|
|
/**
|
|
* Return markdown string with changelog
|
|
* @param {Map<string, CommitGroup>} groups
|
|
*/
|
|
function getChangeLog(groups) {
|
|
let changelog = "";
|
|
|
|
for (const [typeId, group] of groups) {
|
|
if (group.isEmpty) {
|
|
continue;
|
|
}
|
|
|
|
changelog += `### ${replaceHeader(typeId)}${"\r\n"}`;
|
|
|
|
for (const [scopeId, scope] of group.scopes) {
|
|
if (scope.commits.length) {
|
|
changelog += `- #### ${replaceHeader(scopeId)}${"\r\n"}`;
|
|
changelog += getCommitsList(scope.commits, " ");
|
|
}
|
|
}
|
|
|
|
if (group.commits.length) {
|
|
changelog += getCommitsList(group.commits);
|
|
}
|
|
|
|
changelog += "\r\n" + "\r\n";
|
|
}
|
|
|
|
return changelog.trim();
|
|
}
|
|
|
|
function escapeData(s) {
|
|
return String(s)
|
|
.replace(/%/g, "%25")
|
|
.replace(/\r/g, "%0D")
|
|
.replace(/\n/g, "%0A");
|
|
}
|
|
|
|
try {
|
|
const commits = getCommits();
|
|
const grouped = getGroupedCommits(commits);
|
|
const changelog = getChangeLog(grouped);
|
|
process.stdout.write(
|
|
"::set-output name=release-note::" + escapeData(changelog) + "\r\n"
|
|
);
|
|
// require('fs').writeFileSync('../CHANGELOG.md', changelog, {encoding: 'utf-8'})
|
|
} catch (e) {
|
|
console.error(e);
|
|
process.exit(1);
|
|
}
|