projex
Version:
A command line to manage the workflow
249 lines (247 loc) • 10.1 kB
JavaScript
;
var __importDefault = (this && this.__importDefault) || function (mod) {
return (mod && mod.__esModule) ? mod : { "default": mod };
};
Object.defineProperty(exports, "__esModule", { value: true });
exports.organizeCommitsToChangelog = exports.validateVersion = exports.shouldUpdateChangelog = void 0;
const semver_1 = require("semver");
const constants_1 = require("./constants");
const _shared_1 = require("../../../../shared/index");
const _api_1 = require("../../../../api/index");
const chalk_1 = __importDefault(require("chalk"));
const shouldUpdateChangelog = (releaseType, tagName) => {
return ((constants_1.releaseTypesToUpdateChangelogList.indexOf(releaseType) >= 0 &&
tagName &&
constants_1.tagNamesToUpdateChangelog.indexOf(tagName) >= 0) ||
(0, semver_1.valid)(releaseType));
};
exports.shouldUpdateChangelog = shouldUpdateChangelog;
const validateVersion = (oldVersion, releaseType, tagName) => {
if ((0, semver_1.valid)(releaseType)) {
// If `releaseType` is a valid (semver) version, use it.
const parsedVersion = (0, semver_1.parse)(releaseType);
const newVersion = parsedVersion?.version;
if (!newVersion || !(0, semver_1.gt)(newVersion, oldVersion)) {
const errorMessage = `the new version (${chalk_1.default.bold(newVersion)}) must be greater than the old version (${chalk_1.default.bold(oldVersion)})`;
_shared_1.log.error(_api_1.Colors.ERROR(errorMessage));
throw new Error(errorMessage);
}
return [oldVersion, newVersion];
}
// Else `releaseType` is just a regular release type. Then we increment the actual version.
// Check if releaseType is valid.
if (!constants_1.supportedReleaseTypesList.includes(releaseType)) {
const validReleaseTypes = constants_1.supportedReleaseTypesList.join(', ');
throw new Error(`Invalid release type: ${releaseType}\nValid release types are: ${validReleaseTypes}`);
}
// Check if tagName is valid.
if (tagName && !constants_1.supportedTagNamesList.includes(tagName)) {
const validTagNames = constants_1.supportedTagNamesList.join(', ');
throw new Error(`Invalid release tag: ${tagName}\nValid release tags are: ${validTagNames}`);
}
};
exports.validateVersion = validateVersion;
var ChangelogSection;
(function (ChangelogSection) {
ChangelogSection["BreakingChanges"] = "\u26A0 BREAKING CHANGES";
ChangelogSection["Features"] = "Features";
ChangelogSection["BugFixes"] = "Bug Fixes";
ChangelogSection["PerformanceImprovements"] = "Performance Improvements";
ChangelogSection["Reverts"] = "Reverts";
ChangelogSection["MiscellaneousChores"] = "Miscellaneous Chores";
ChangelogSection["Documentation"] = "Documentation";
ChangelogSection["Styles"] = "Styles";
ChangelogSection["CodeRefactoring"] = "Code Refactoring";
ChangelogSection["Tests"] = "Tests";
ChangelogSection["BuildSystem"] = "Build System";
ChangelogSection["ContinuousIntegration"] = "Continuous Integration";
})(ChangelogSection || (ChangelogSection = {}));
const CHANGELOG_SECTIONS = [
{ type: 'feat', section: ChangelogSection.Features },
{ type: 'fix', section: ChangelogSection.BugFixes },
{ type: 'perf', section: ChangelogSection.PerformanceImprovements },
{ type: 'revert', section: ChangelogSection.Reverts },
{ type: 'chore', section: ChangelogSection.MiscellaneousChores },
{ type: 'docs', section: ChangelogSection.Documentation },
{ type: 'style', section: ChangelogSection.Styles },
{ type: 'refactor', section: ChangelogSection.CodeRefactoring },
{ type: 'test', section: ChangelogSection.Tests },
{ type: 'build', section: ChangelogSection.BuildSystem },
{ type: 'ci', section: ChangelogSection.ContinuousIntegration },
{ type: 'breaking', section: ChangelogSection.BreakingChanges },
];
const normalizeCommit = (commits, originUrl, section) => {
return commits.map((commit) => {
const commitMessage = commit.slice(41);
const message = `* ${commitMessage}`;
if (section === ChangelogSection.BreakingChanges) {
return message;
}
const commitId = commit.slice(0, 40);
return `${message} ([${commitId.slice(0, 8)}](${originUrl}/commit/${commitId}))`;
});
};
const getScopeInCommit = (commit) => {
let regex = /\(([\w\s-]*)\)/;
let match = commit.match(regex);
if (match) {
const scope = match[1];
return scope ? `**${scope}**: ` : '';
}
else {
return '';
}
};
const getPullRequestLink = (originUrl, pullRequestId) => {
let page = '';
if (originUrl.includes('github')) {
page = 'pull';
}
else {
page = 'pullrequest';
}
return `${originUrl}/${page}/${pullRequestId}`;
};
const getPullRequestIdFromAzure = (commit, originUrl) => {
let regex = /merged pr\s+(\d+)(\s|:\s)/;
let match = commit.match(regex);
if (match) {
const text = match[0];
const pullRequestId = match[1];
// Remove "Merged PR" text
const withoutMergedText = commit.replace(text, '');
const url = getPullRequestLink(originUrl, pullRequestId);
return `${withoutMergedText} ([#${pullRequestId}](${url}))`;
}
else {
return commit;
}
};
const getPullRequestIdFromGithub = (commit, originUrl) => {
let regex = /\(#(\d+)\)/;
let match = commit.match(regex);
if (match) {
const text = match[0];
// remove parentheses
const withoutParentheses = text.replace(/[\(\)]/g, '');
// remove # symbol
const pullRequestId = withoutParentheses.replace('#', '');
const url = getPullRequestLink(originUrl, pullRequestId);
return commit.replace(text, `([${withoutParentheses}](${url}))`);
}
else {
return commit;
}
};
const getPullRequestCommit = (commit, originUrl) => {
const commitWithGithubId = getPullRequestIdFromGithub(commit, originUrl);
if (commitWithGithubId != commit) {
return commitWithGithubId;
}
else {
return getPullRequestIdFromAzure(commit, originUrl);
}
};
const getUnReleasedChanges = (changelogContent) => {
let regex = /.*:\s*release\s*(v\d+\.\d+\.\d+)(\s+.*|$)/;
let lines = changelogContent.split('\n');
let result = '';
for (let line of lines) {
if (regex.test(line)) {
break;
}
result += line + '\n';
}
if (!result || result == '') {
_shared_1.log.error(_api_1.Colors.ERROR('no unreleased changes found, please check your commits.'));
process.exit(1);
}
return result;
};
const generateChangelogSectionForTitle = (section, commits, originUrl) => {
const normalizedCommits = normalizeCommit(commits, originUrl, section).join('\n');
return `
### ${section}
${normalizedCommits}
`;
};
const generateChangelogSection = (changes, originUrl) => {
const sections = new Map();
CHANGELOG_SECTIONS.forEach((section) => sections.set(section.section, []));
changes.forEach((change) => {
const section = sections.get(change.section);
if (section) {
section.push(change.commit);
}
});
let changelog = '';
sections.forEach((commits, section) => {
if (commits.length > 0) {
changelog += generateChangelogSectionForTitle(section, commits, originUrl);
}
});
return changelog;
};
const determineTypeChange = (commit) => {
const changes = [];
for (let section of CHANGELOG_SECTIONS) {
// Get the type of the commit
let regex = new RegExp(`(${section.type})(\\([\\w\\s-]*\\))?:\\s*`);
let match = commit.match(regex);
// Get the breaking change
let breakingChangeRegex = new RegExp(`(${section.type})(\\([\\w\\s-]*\\))?!:\\s*`);
let breakingChangeMatch = commit.match(breakingChangeRegex);
const scope = getScopeInCommit(commit);
if (match) {
changes.push({
section: section.section,
commit: commit.replace(match[0], scope),
});
}
if (breakingChangeMatch) {
changes.push({
commit: commit.replace(breakingChangeMatch[0], scope),
section: ChangelogSection.BreakingChanges,
});
const typeChange = breakingChangeMatch[1];
const typeBreakingChange = CHANGELOG_SECTIONS.find((section) => section.type === typeChange);
if (typeBreakingChange) {
changes.push({
commit: commit.replace(breakingChangeMatch[0], scope),
section: typeBreakingChange.section,
});
}
}
}
if (!changes.length) {
changes.push({
commit,
section: ChangelogSection.Features,
});
}
return changes;
};
const determineReleaseType = (changes) => {
const haveBreakingChanges = changes.some((change) => change.section === ChangelogSection.BreakingChanges);
const haveChangesExcludingBugFixes = changes.some((change) => change.section !== ChangelogSection.BugFixes && change.section !== ChangelogSection.BreakingChanges);
return haveBreakingChanges ? 'major' : haveChangesExcludingBugFixes ? 'minor' : 'patch';
};
const organizeCommitsToChangelog = (commits, originUrl) => {
const unReleasedCommits = getUnReleasedChanges(commits);
const changes = [];
// Split commits by line and group them by type
unReleasedCommits.split('\n').forEach((commit) => {
if (commit === '')
return;
commit = commit.toLowerCase();
commit = getPullRequestCommit(commit, originUrl);
changes.push(...determineTypeChange(commit));
});
const changelog = generateChangelogSection(changes, originUrl);
const releaseType = determineReleaseType(changes);
return {
changelog,
releaseType,
};
};
exports.organizeCommitsToChangelog = organizeCommitsToChangelog;