Skip to content

Commit

Permalink
feat: generate changelog based on Conventional Commits
Browse files Browse the repository at this point in the history
  • Loading branch information
Kevin-de-Jong committed Jun 19, 2023
1 parent 66f1dee commit 3413c6c
Show file tree
Hide file tree
Showing 6 changed files with 521 additions and 23 deletions.
52 changes: 52 additions & 0 deletions lib/changelog.d.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,52 @@
import { IConventionalCommit } from "@dev-build-deploy/commit-it";
import { SemVer } from "./semver";
type semVerBumpTypes = "major" | "minor" | "patch" | "none";
/**
* Exclude configuration
* @interface IExclude
* @member bump Exclude commits from the changelog based on the bump type
* @member types Exclude commits from the changelog based on the type
* @member scopes Exclude commits from the changelog based on the scope
*/
interface IExclude {
bump?: semVerBumpTypes[];
types?: string[];
scopes?: string[];
}
/**
* Release configuration
* @interface IReleaseConfiguration
* @member changelog Changelog configuration
* @member changelog.exclude Exclude commits from the changelog
* @member changelog.categories Categories to use in the changelog
* @member changelog.categories.title Title of the category
* @member changelog.categories.bump Bump type for the category
* @member changelog.categories.types Types to include in the category
* @member changelog.categories.scopes Scopes to include in the category
* @member changelog.categories.exclude Exclude commits from the category
*/
interface IReleaseConfiguration {
changelog?: {
exclude?: IExclude;
categories?: {
title: string;
bump?: semVerBumpTypes[];
types?: string[];
scopes?: string[];
exclude?: IExclude;
}[];
};
}
/**
* Get the configuration for the Changelog
* @returns
*/
export declare function getConfiguration(): IReleaseConfiguration;
/**
* Generate the changelog based on the provided version and commits
* @param version SemVer version of the Release
* @param commits Conventional Commits part of the Changelog
* @returns Changelog in Markdown format
*/
export declare function generateChangelog(version: SemVer, commits: IConventionalCommit[]): string;
export {};
146 changes: 138 additions & 8 deletions lib/index.js
Original file line number Diff line number Diff line change
Expand Up @@ -10572,11 +10572,12 @@ var __awaiter = (this && this.__awaiter) || function (thisArg, _arguments, P, ge
});
};
Object.defineProperty(exports, "__esModule", ({ value: true }));
exports.run = exports.determineBumpType = void 0;
exports.run = exports.filterConventionalCommits = exports.determineBumpType = void 0;
const core = __importStar(__nccwpck_require__(2186));
const github = __importStar(__nccwpck_require__(5438));
const semver_1 = __nccwpck_require__(8593);
const commit_it_1 = __nccwpck_require__(9403);
const changelog_1 = __nccwpck_require__(8598);
/**
* Retrieve GitHub Releases, sorted by SemVer
* @returns List of releases
Expand Down Expand Up @@ -10610,10 +10611,9 @@ function determineBumpType(commits) {
const typeCount = { feat: 0, fix: 0 };
for (const commit of commits) {
try {
const conventionalCommit = (0, commit_it_1.getConventionalCommit)(commit);
if (conventionalCommit.breaking)
if (commit.breaking)
return "major";
typeCount[conventionalCommit.type]++;
typeCount[commit.type]++;
}
catch (error) {
if (!(error instanceof commit_it_1.ConventionalCommitError))
Expand All @@ -10627,9 +10627,29 @@ function determineBumpType(commits) {
return;
}
exports.determineBumpType = determineBumpType;
function createRelease(version) {
/**
* Filters the provided commits to only include Conventional Commits
* @param commits The commits to filter
* @returns List of Conventional Commits
* @internal
*/
function filterConventionalCommits(commits) {
return commits
.map(c => {
try {
return (0, commit_it_1.getConventionalCommit)(c);
}
catch (error) {
if (!(error instanceof commit_it_1.ConventionalCommitError))
throw error;
}
})
.filter(c => c !== undefined);
}
exports.filterConventionalCommits = filterConventionalCommits;
function createRelease(version, commits) {
const octokit = github.getOctokit(core.getInput("token"));
return octokit.rest.repos.createRelease(Object.assign({ name: version.toString(), body: "ReleaseMe", draft: false, prerelease: version.preRelease !== undefined, make_latest: version.preRelease === undefined ? "true" : "false", generate_release_notes: false, discussion_category_name: undefined, tag_name: version.toString(), target_commitish: github.context.ref }, github.context.repo));
return octokit.rest.repos.createRelease(Object.assign({ name: version.toString(), body: (0, changelog_1.generateChangelog)(version, commits), draft: false, prerelease: version.preRelease !== undefined, make_latest: version.preRelease === undefined ? "true" : "false", generate_release_notes: false, discussion_category_name: undefined, tag_name: version.toString(), target_commitish: github.context.ref }, github.context.repo));
}
/**
* Determines whether the current branch is the default branch.
Expand Down Expand Up @@ -10663,7 +10683,8 @@ function run() {
core.info(`ℹ️ Latest release: ${latestRelease.tag_name}`);
const delta = yield getChangesSinceRelease(latestRelease.tag_name);
core.info(`ℹ️ Changes since latest release: ${delta.length} commits`);
const bump = determineBumpType(delta);
const commits = filterConventionalCommits(delta);
const bump = determineBumpType(commits);
if (bump === undefined) {
core.info("⚠️ No bump required, skipping...");
core.endGroup();
Expand All @@ -10673,7 +10694,7 @@ function run() {
core.startGroup("📝 Creating GitHub Release");
const newVersion = new semver_1.SemVer(latestRelease.tag_name).bump(bump);
core.info(`Next version will be: ${newVersion}`);
yield createRelease(newVersion);
yield createRelease(newVersion, commits);
core.setOutput("new-version", newVersion);
core.endGroup();
}
Expand All @@ -10685,6 +10706,115 @@ function run() {
exports.run = run;


/***/ }),

/***/ 8598:
/***/ (function(__unused_webpack_module, exports, __nccwpck_require__) {

"use strict";

/*
SPDX-FileCopyrightText: 2023 Kevin de Jong <monkaii@hotmail.com>

SPDX-License-Identifier: GPL-3.0-or-later
*/
var __createBinding = (this && this.__createBinding) || (Object.create ? (function(o, m, k, k2) {
if (k2 === undefined) k2 = k;
var desc = Object.getOwnPropertyDescriptor(m, k);
if (!desc || ("get" in desc ? !m.__esModule : desc.writable || desc.configurable)) {
desc = { enumerable: true, get: function() { return m[k]; } };
}
Object.defineProperty(o, k2, desc);
}) : (function(o, m, k, k2) {
if (k2 === undefined) k2 = k;
o[k2] = m[k];
}));
var __setModuleDefault = (this && this.__setModuleDefault) || (Object.create ? (function(o, v) {
Object.defineProperty(o, "default", { enumerable: true, value: v });
}) : function(o, v) {
o["default"] = v;
});
var __importStar = (this && this.__importStar) || function (mod) {
if (mod && mod.__esModule) return mod;
var result = {};
if (mod != null) for (var k in mod) if (k !== "default" && Object.prototype.hasOwnProperty.call(mod, k)) __createBinding(result, mod, k);
__setModuleDefault(result, mod);
return result;
};
Object.defineProperty(exports, "__esModule", ({ value: true }));
exports.generateChangelog = exports.getConfiguration = void 0;
const action_1 = __nccwpck_require__(7672);
const thisModule = __importStar(__nccwpck_require__(8598));
/**
* Get the configuration for the Changelog
* @returns
*/
function getConfiguration() {
// TODO: Read configuration from file (.github/release.y[a]ml)
return {
changelog: {
categories: [
{ title: "💥 Breaking Changes", bump: ["major"] },
{ title: "✨ New Features", bump: ["minor"] },
{ title: "🐛 Bug Fixes", bump: ["patch"] },
],
},
};
}
exports.getConfiguration = getConfiguration;
/**
* First character to upper case
* @param value
* @returns
*/
function firstCharToUpperCase(value) {
return value.charAt(0).toUpperCase() + value.slice(1);
}
/**
* Generate the changelog based on the provided version and commits
* @param version SemVer version of the Release
* @param commits Conventional Commits part of the Changelog
* @returns Changelog in Markdown format
*/
function generateChangelog(version, commits) {
var _a, _b;
const config = thisModule.getConfiguration();
const changelog = `## What's Changed\n\n${(_b = (_a = config.changelog) === null || _a === void 0 ? void 0 : _a.categories) === null || _b === void 0 ? void 0 : _b.map(category => {
const categoryCommits = commits.filter(commit => {
var _a, _b, _c, _d, _e, _f, _g, _h, _j, _k, _l, _m, _o, _p, _q;
if (category.bump && !category.bump.includes((_a = (0, action_1.determineBumpType)([commit])) !== null && _a !== void 0 ? _a : "none"))
return false;
if (((_b = category.exclude) === null || _b === void 0 ? void 0 : _b.bump) &&
category.exclude.bump.includes((_c = (0, action_1.determineBumpType)([commit])) !== null && _c !== void 0 ? _c : "none"))
return false;
if (((_e = (_d = config.changelog) === null || _d === void 0 ? void 0 : _d.exclude) === null || _e === void 0 ? void 0 : _e.bump) &&
config.changelog.exclude.bump.includes((_f = (0, action_1.determineBumpType)([commit])) !== null && _f !== void 0 ? _f : "none"))
return false;
if (category.types && !category.types.includes(commit.type))
return false;
if (((_g = category.exclude) === null || _g === void 0 ? void 0 : _g.types) && category.exclude.types.includes(commit.type))
return false;
if (((_j = (_h = config.changelog) === null || _h === void 0 ? void 0 : _h.exclude) === null || _j === void 0 ? void 0 : _j.types) && config.changelog.exclude.types.includes(commit.type))
return false;
if (category.scopes && !category.scopes.includes((_k = commit.scope) !== null && _k !== void 0 ? _k : "none"))
return false;
if (((_l = category.exclude) === null || _l === void 0 ? void 0 : _l.scopes) && category.exclude.scopes.includes((_m = commit.scope) !== null && _m !== void 0 ? _m : "none"))
return false;
if (((_p = (_o = config.changelog) === null || _o === void 0 ? void 0 : _o.exclude) === null || _p === void 0 ? void 0 : _p.scopes) && config.changelog.exclude.scopes.includes((_q = commit.scope) !== null && _q !== void 0 ? _q : "none"))
return false;
return true;
});
if (categoryCommits.length === 0)
return "";
return `## ${category.title}\n\n${categoryCommits
.map(commit => `- ${firstCharToUpperCase(commit.description)}`)
.join("\n")}\n\n`;
}).join("\n")}`;
return changelog;
}
exports.generateChangelog = generateChangelog;


/***/ }),

/***/ 8593:
Expand Down
47 changes: 37 additions & 10 deletions src/action.ts
Original file line number Diff line number Diff line change
Expand Up @@ -7,7 +7,15 @@ SPDX-License-Identifier: GPL-3.0-or-later
import * as core from "@actions/core";
import * as github from "@actions/github";
import { SemVer, sortSemVer } from "./semver";
import { ConventionalCommitError, ICommit, getCommit, getConventionalCommit } from "@dev-build-deploy/commit-it";
import {
ConventionalCommitError,
ICommit,
IConventionalCommit,
getCommit,
getConventionalCommit,
isConventionalCommit,
} from "@dev-build-deploy/commit-it";
import { generateChangelog } from "./changelog";

/**
* Retrieve GitHub Releases, sorted by SemVer
Expand Down Expand Up @@ -42,14 +50,13 @@ async function getChangesSinceRelease(tag: string): Promise<ICommit[]> {
* @returns The bump type ("major", "minor", "patch" or undefined)
* @internal
*/
export function determineBumpType(commits: ICommit[]): "major" | "minor" | "patch" | undefined {
export function determineBumpType(commits: IConventionalCommit[]): "major" | "minor" | "patch" | undefined {
const typeCount: { [key: string]: number } = { feat: 0, fix: 0 };

for (const commit of commits) {
try {
const conventionalCommit = getConventionalCommit(commit);
if (conventionalCommit.breaking) return "major";
typeCount[conventionalCommit.type]++;
if (commit.breaking) return "major";
typeCount[commit.type]++;
} catch (error) {
if (!(error instanceof ConventionalCommitError)) throw error;
}
Expand All @@ -61,12 +68,30 @@ export function determineBumpType(commits: ICommit[]): "major" | "minor" | "patc
return;
}

function createRelease(version: SemVer) {
/**
* Filters the provided commits to only include Conventional Commits
* @param commits The commits to filter
* @returns List of Conventional Commits
* @internal
*/
export function filterConventionalCommits(commits: ICommit[]): IConventionalCommit[] {
return commits
.map(c => {
try {
return getConventionalCommit(c);
} catch (error) {
if (!(error instanceof ConventionalCommitError)) throw error;
}
})
.filter(c => c !== undefined) as IConventionalCommit[];
}

function createRelease(version: SemVer, commits: IConventionalCommit[]) {
const octokit = github.getOctokit(core.getInput("token"));

return octokit.rest.repos.createRelease({
name: version.toString(),
body: "ReleaseMe",
body: generateChangelog(version, commits),
draft: false,
prerelease: version.preRelease !== undefined,
make_latest: version.preRelease === undefined ? "true" : "false",
Expand Down Expand Up @@ -113,7 +138,9 @@ export async function run(): Promise<void> {
const delta = await getChangesSinceRelease(latestRelease.tag_name);
core.info(`ℹ️ Changes since latest release: ${delta.length} commits`);

const bump = determineBumpType(delta);
const commits = filterConventionalCommits(delta);
const bump = determineBumpType(commits);

if (bump === undefined) {
core.info("⚠️ No bump required, skipping...");
core.endGroup();
Expand All @@ -124,10 +151,10 @@ export async function run(): Promise<void> {
core.startGroup("📝 Creating GitHub Release");
const newVersion = new SemVer(latestRelease.tag_name).bump(bump);
core.info(`Next version will be: ${newVersion}`);
await createRelease(newVersion);
await createRelease(newVersion, commits);
core.setOutput("new-version", newVersion);
core.endGroup();
} catch (ex) {
core.setFailed((ex as Error).message);
}
}
}
Loading

0 comments on commit 3413c6c

Please sign in to comment.