Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
7 changes: 7 additions & 0 deletions schemas/config.json
Original file line number Diff line number Diff line change
Expand Up @@ -20,6 +20,13 @@
"description": "Feature changes only bump semver patch if version < 1.0.0",
"type": "boolean"
},
"extra-prefix-mapping": {
"description": "Map custom commit prefixes to conventional commit types. Use empty string (\"\") to map non-conventional commits. Example: {\"change\": \"fix\", \"\": \"chore\"}",
"type": "object",
"additionalProperties": {
"type": "string"
}
},
"prerelease-type": {
"description": "Configuration option for the prerelease versioning strategy. If prerelease strategy used and type set, will set the prerelease part of the version to the provided value in case prerelease part is not present.",
"type": "string"
Expand Down
37 changes: 35 additions & 2 deletions src/commit.ts
Original file line number Diff line number Diff line change
Expand Up @@ -399,14 +399,17 @@ function splitMessages(message: string): string[] {
* Given a list of raw commits, parse and expand into conventional commits.
*
* @param commits {Commit[]} The input commits
* @param logger {Logger} The logger to use for debug messages
* @param extraPrefixMapping {Record<string, string>} Map custom prefixes to conventional commit types. Use empty string ("") for non-conventional commits.
*
* @returns {ConventionalCommit[]} Parsed and expanded commits. There may be
* more commits returned as a single raw commit may contain multiple release
* messages.
*/
export function parseConventionalCommits(
commits: Commit[],
logger: Logger = defaultLogger
logger: Logger = defaultLogger,
extraPrefixMapping?: Record<string, string>
): ConventionalCommit[] {
const conventionalCommits: ConventionalCommit[] = [];

Expand All @@ -419,12 +422,22 @@ export function parseConventionalCommits(
const breaking =
parsedCommit.notes.filter(note => note.title === 'BREAKING CHANGE')
.length > 0;

// Check if the parsed type should be remapped via extraPrefixMapping
let finalType = parsedCommit.type;
if (extraPrefixMapping && parsedCommit.type in extraPrefixMapping) {
finalType = extraPrefixMapping[parsedCommit.type];
logger.debug(
`remapping commit type '${parsedCommit.type}' to '${finalType}': ${commit.sha}`
);
}

conventionalCommits.push({
sha: commit.sha,
message: parsedCommit.header,
files: commit.files,
pullRequest: commit.pullRequest,
type: parsedCommit.type,
type: finalType,
scope: parsedCommit.scope,
bareMessage: parsedCommit.subject,
notes: parsedCommit.notes,
Expand All @@ -439,6 +452,26 @@ export function parseConventionalCommits(
}`
);
logger.debug(`error message: ${_err}`);
// Check for empty string mapping (non-conventional commits)
if (extraPrefixMapping && '' in extraPrefixMapping) {
const mappedType = extraPrefixMapping[''];
const bareMessage = commitMessage.split('\n')[0];
logger.debug(
`treating non-conventional commit as '${mappedType}': ${commit.sha}`
);
conventionalCommits.push({
sha: commit.sha,
message: commitMessage,
files: commit.files,
pullRequest: commit.pullRequest,
type: mappedType,
scope: null,
bareMessage,
notes: [],
references: [],
breaking: false,
});
}
}
}
}
Expand Down
6 changes: 5 additions & 1 deletion src/manifest.ts
Original file line number Diff line number Diff line change
Expand Up @@ -101,6 +101,7 @@ export interface ReleaserConfig {
bumpMinorPreMajor?: boolean;
bumpPatchForMinorPreMajor?: boolean;
prereleaseType?: string;
extraPrefixMapping?: Record<string, string>;

// Strategy options
releaseAs?: string;
Expand Down Expand Up @@ -161,6 +162,7 @@ interface ReleaserConfigJson {
'bump-minor-pre-major'?: boolean;
'bump-patch-for-minor-pre-major'?: boolean;
'prerelease-type'?: string;
'extra-prefix-mapping'?: Record<string, string>;
'changelog-sections'?: ChangelogSection[];
'release-as'?: string;
'skip-github-release'?: boolean;
Expand Down Expand Up @@ -735,7 +737,8 @@ export class Manifest {
this.logger.debug(`targetBranch: ${this.targetBranch}`);
let pathCommits = parseConventionalCommits(
commitsPerPath[path],
this.logger
this.logger,
config.extraPrefixMapping
);
// The processCommits hook can be implemented by plugins to
// post-process commits. This can be used to perform cleanup, e.g,, sentence
Expand Down Expand Up @@ -1379,6 +1382,7 @@ function extractReleaserConfig(
bumpMinorPreMajor: config['bump-minor-pre-major'],
bumpPatchForMinorPreMajor: config['bump-patch-for-minor-pre-major'],
prereleaseType: config['prerelease-type'],
extraPrefixMapping: config['extra-prefix-mapping'],
versioning: config['versioning'],
changelogSections: config['changelog-sections'],
changelogPath: config['changelog-path'],
Expand Down
85 changes: 85 additions & 0 deletions test/commits.ts
Original file line number Diff line number Diff line change
Expand Up @@ -282,6 +282,91 @@ describe('parseConventionalCommits', () => {
// expect(conventionalCommits[0].type).to.equal('docs');
// expect(conventionalCommits[0].scope).is.null;
// });

it('ignores non-conventional commits by default', async () => {
const commits = [
buildMockCommit('feat: some feature'),
buildMockCommit('this is a non-conventional commit'),
buildMockCommit('fix: some bugfix'),
];
const conventionalCommits = parseConventionalCommits(commits);
expect(conventionalCommits).lengthOf(2);
expect(conventionalCommits[0].type).to.equal('feat');
expect(conventionalCommits[1].type).to.equal('fix');
});

it('treats non-conventional commits as fix when mapped with empty string', async () => {
const commits = [
buildMockCommit('feat: some feature'),
buildMockCommit('this is a non-conventional commit'),
buildMockCommit('fix: some bugfix'),
];
const conventionalCommits = parseConventionalCommits(commits, undefined, {
'': 'fix',
});
expect(conventionalCommits).lengthOf(3);
expect(conventionalCommits[0].type).to.equal('feat');
expect(conventionalCommits[1].type).to.equal('fix');
expect(conventionalCommits[1].bareMessage).to.equal(
'this is a non-conventional commit'
);
expect(conventionalCommits[1].scope).is.null;
expect(conventionalCommits[1].breaking).to.be.false;
expect(conventionalCommits[2].type).to.equal('fix');
});

it('preserves files, empty notes and references when using prefix mapping', async () => {
const commits = [
buildMockCommit('non-conventional commit', [
'path1/file1.txt',
'path2/file2.txt',
]),
];
const conventionalCommits = parseConventionalCommits(commits, undefined, {
'': 'fix',
});
expect(conventionalCommits).lengthOf(1);
expect(conventionalCommits[0].type).to.equal('fix');
expect(conventionalCommits[0].files).to.deep.equal([
'path1/file1.txt',
'path2/file2.txt',
]);
expect(conventionalCommits[0].notes).to.be.empty;
expect(conventionalCommits[0].references).to.be.empty;
expect(conventionalCommits[0].breaking).to.be.false;
});

it('maps custom prefix "change" to "fix"', async () => {
const commits = [
buildMockCommit('change: update documentation'),
buildMockCommit('feat: add new feature'),
];
const conventionalCommits = parseConventionalCommits(commits, undefined, {
change: 'fix',
});
expect(conventionalCommits).lengthOf(2);
expect(conventionalCommits[0].type).to.equal('fix');
expect(conventionalCommits[0].bareMessage).to.equal('update documentation');
expect(conventionalCommits[1].type).to.equal('feat');
});

it('maps emoji prefix to conventional commit type', async () => {
const commits = [
buildMockCommit('🐛: fix the bug'),
buildMockCommit('✨: add new feature'),
buildMockCommit('feat: regular feature'),
];
const conventionalCommits = parseConventionalCommits(commits, undefined, {
'🐛': 'fix',
'✨': 'feat',
});
expect(conventionalCommits).lengthOf(3);
expect(conventionalCommits[0].type).to.equal('fix');
expect(conventionalCommits[0].bareMessage).to.equal('fix the bug');
expect(conventionalCommits[1].type).to.equal('feat');
expect(conventionalCommits[1].bareMessage).to.equal('add new feature');
expect(conventionalCommits[2].type).to.equal('feat');
});
});

function assertHasCommit(
Expand Down
45 changes: 45 additions & 0 deletions test/util/filter-commits.ts
Original file line number Diff line number Diff line change
Expand Up @@ -89,4 +89,49 @@ describe('filterCommits', () => {
]);
expect(commits.length).to.equal(1);
});
it('includes commits with fix type', () => {
const commits = filterCommits([
{
type: 'fix',
notes: [],
references: [],
bareMessage: 'update readme',
message: 'update readme',
scope: null,
breaking: false,
sha: 'abc123',
},
]);
expect(commits.length).to.equal(1);
});
it('includes commits with feat type', () => {
const commits = filterCommits([
{
type: 'feat',
notes: [],
references: [],
bareMessage: 'add new feature',
message: 'add new feature',
scope: null,
breaking: false,
sha: 'def456',
},
]);
expect(commits.length).to.equal(1);
});
it('excludes commits with chore type', () => {
const commits = filterCommits([
{
type: 'chore',
notes: [],
references: [],
bareMessage: 'update dependencies',
message: 'update dependencies',
scope: null,
breaking: false,
sha: 'ghi789',
},
]);
expect(commits.length).to.equal(0);
});
});
Loading