Skip to content

Commit 1d8cc63

Browse files
committed
feat: add non-conventional-commit-type setting for legacy commits
Add optional `non-conventional-commit-type` configuration setting that treats non-conventional commits as a specified commit type (e.g., 'fix', 'feat', 'chore'). This enables version bumping when merging legacy code that doesn't follow conventional commit standards. Fully backward compatible - feature disabled by default. Fixes #2623.
1 parent 6a9ddb7 commit 1d8cc63

File tree

5 files changed

+147
-2
lines changed

5 files changed

+147
-2
lines changed

schemas/config.json

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -20,6 +20,10 @@
2020
"description": "Feature changes only bump semver patch if version < 1.0.0",
2121
"type": "boolean"
2222
},
23+
"non-conventional-commit-type": {
24+
"description": "Treat non-conventional commits as if they were conventional commits with the specified type (e.g., 'fix', 'feat', 'chore'). When not set, non-conventional commits are ignored.",
25+
"type": "string"
26+
},
2327
"prerelease-type": {
2428
"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.",
2529
"type": "string"

src/commit.ts

Lines changed: 22 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -399,14 +399,17 @@ function splitMessages(message: string): string[] {
399399
* Given a list of raw commits, parse and expand into conventional commits.
400400
*
401401
* @param commits {Commit[]} The input commits
402+
* @param logger {Logger} The logger to use for debug messages
403+
* @param nonConventionalCommitType {string} If set, treat non-conventional commits as this commit type (e.g., 'fix', 'feat')
402404
*
403405
* @returns {ConventionalCommit[]} Parsed and expanded commits. There may be
404406
* more commits returned as a single raw commit may contain multiple release
405407
* messages.
406408
*/
407409
export function parseConventionalCommits(
408410
commits: Commit[],
409-
logger: Logger = defaultLogger
411+
logger: Logger = defaultLogger,
412+
nonConventionalCommitType?: string
410413
): ConventionalCommit[] {
411414
const conventionalCommits: ConventionalCommit[] = [];
412415

@@ -439,6 +442,24 @@ export function parseConventionalCommits(
439442
}`
440443
);
441444
logger.debug(`error message: ${_err}`);
445+
// If nonConventionalCommitType is set, treat non-conventional commits as the specified type
446+
if (nonConventionalCommitType) {
447+
logger.debug(
448+
`treating non-conventional commit as '${nonConventionalCommitType}': ${commit.sha}`
449+
);
450+
conventionalCommits.push({
451+
sha: commit.sha,
452+
message: commitMessage,
453+
files: commit.files,
454+
pullRequest: commit.pullRequest,
455+
type: nonConventionalCommitType,
456+
scope: null,
457+
bareMessage: commitMessage.split('\n')[0],
458+
notes: [],
459+
references: [],
460+
breaking: false,
461+
});
462+
}
442463
}
443464
}
444465
}

src/manifest.ts

Lines changed: 5 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -101,6 +101,7 @@ export interface ReleaserConfig {
101101
bumpMinorPreMajor?: boolean;
102102
bumpPatchForMinorPreMajor?: boolean;
103103
prereleaseType?: string;
104+
nonConventionalCommitType?: string;
104105

105106
// Strategy options
106107
releaseAs?: string;
@@ -160,6 +161,7 @@ interface ReleaserConfigJson {
160161
'bump-minor-pre-major'?: boolean;
161162
'bump-patch-for-minor-pre-major'?: boolean;
162163
'prerelease-type'?: string;
164+
'non-conventional-commit-type'?: string;
163165
'changelog-sections'?: ChangelogSection[];
164166
'release-as'?: string;
165167
'skip-github-release'?: boolean;
@@ -733,7 +735,8 @@ export class Manifest {
733735
this.logger.debug(`targetBranch: ${this.targetBranch}`);
734736
let pathCommits = parseConventionalCommits(
735737
commitsPerPath[path],
736-
this.logger
738+
this.logger,
739+
config.nonConventionalCommitType
737740
);
738741
// The processCommits hook can be implemented by plugins to
739742
// post-process commits. This can be used to perform cleanup, e.g,, sentence
@@ -1377,6 +1380,7 @@ function extractReleaserConfig(
13771380
bumpMinorPreMajor: config['bump-minor-pre-major'],
13781381
bumpPatchForMinorPreMajor: config['bump-patch-for-minor-pre-major'],
13791382
prereleaseType: config['prerelease-type'],
1383+
nonConventionalCommitType: config['non-conventional-commit-type'],
13801384
versioning: config['versioning'],
13811385
changelogSections: config['changelog-sections'],
13821386
changelogPath: config['changelog-path'],

test/commits.ts

Lines changed: 71 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -282,6 +282,77 @@ describe('parseConventionalCommits', () => {
282282
// expect(conventionalCommits[0].type).to.equal('docs');
283283
// expect(conventionalCommits[0].scope).is.null;
284284
// });
285+
286+
it('ignores non-conventional commits by default', async () => {
287+
const commits = [
288+
buildMockCommit('feat: some feature'),
289+
buildMockCommit('this is a non-conventional commit'),
290+
buildMockCommit('fix: some bugfix'),
291+
];
292+
const conventionalCommits = parseConventionalCommits(commits);
293+
expect(conventionalCommits).lengthOf(2);
294+
expect(conventionalCommits[0].type).to.equal('feat');
295+
expect(conventionalCommits[1].type).to.equal('fix');
296+
});
297+
298+
it('treats non-conventional commits as fix when alias is "fix"', async () => {
299+
const commits = [
300+
buildMockCommit('feat: some feature'),
301+
buildMockCommit('this is a non-conventional commit'),
302+
buildMockCommit('fix: some bugfix'),
303+
];
304+
const conventionalCommits = parseConventionalCommits(
305+
commits,
306+
undefined,
307+
'fix'
308+
);
309+
expect(conventionalCommits).lengthOf(3);
310+
expect(conventionalCommits[0].type).to.equal('feat');
311+
expect(conventionalCommits[1].type).to.equal('fix');
312+
expect(conventionalCommits[1].bareMessage).to.equal(
313+
'this is a non-conventional commit'
314+
);
315+
expect(conventionalCommits[1].scope).is.null;
316+
expect(conventionalCommits[1].breaking).to.be.false;
317+
expect(conventionalCommits[2].type).to.equal('fix');
318+
});
319+
320+
it('treats non-conventional commits as chore when alias is "chore"', async () => {
321+
const commits = [buildMockCommit('regular commit message without type')];
322+
const conventionalCommits = parseConventionalCommits(
323+
commits,
324+
undefined,
325+
'chore'
326+
);
327+
expect(conventionalCommits).lengthOf(1);
328+
expect(conventionalCommits[0].type).to.equal('chore');
329+
expect(conventionalCommits[0].bareMessage).to.equal(
330+
'regular commit message without type'
331+
);
332+
});
333+
334+
it('preserves files, empty notes and references when using non-conventional alias', async () => {
335+
const commits = [
336+
buildMockCommit('non-conventional commit', [
337+
'path1/file1.txt',
338+
'path2/file2.txt',
339+
]),
340+
];
341+
const conventionalCommits = parseConventionalCommits(
342+
commits,
343+
undefined,
344+
'fix'
345+
);
346+
expect(conventionalCommits).lengthOf(1);
347+
expect(conventionalCommits[0].type).to.equal('fix');
348+
expect(conventionalCommits[0].files).to.deep.equal([
349+
'path1/file1.txt',
350+
'path2/file2.txt',
351+
]);
352+
expect(conventionalCommits[0].notes).to.be.empty;
353+
expect(conventionalCommits[0].references).to.be.empty;
354+
expect(conventionalCommits[0].breaking).to.be.false;
355+
});
285356
});
286357

287358
function assertHasCommit(

test/util/filter-commits.ts

Lines changed: 45 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -89,4 +89,49 @@ describe('filterCommits', () => {
8989
]);
9090
expect(commits.length).to.equal(1);
9191
});
92+
it('includes non-conventional commits converted to fix type', () => {
93+
const commits = filterCommits([
94+
{
95+
type: 'fix',
96+
notes: [],
97+
references: [],
98+
bareMessage: 'update readme',
99+
message: 'update readme',
100+
scope: null,
101+
breaking: false,
102+
sha: 'abc123',
103+
},
104+
]);
105+
expect(commits.length).to.equal(1);
106+
});
107+
it('includes non-conventional commits converted to feat type', () => {
108+
const commits = filterCommits([
109+
{
110+
type: 'feat',
111+
notes: [],
112+
references: [],
113+
bareMessage: 'add new feature',
114+
message: 'add new feature',
115+
scope: null,
116+
breaking: false,
117+
sha: 'def456',
118+
},
119+
]);
120+
expect(commits.length).to.equal(1);
121+
});
122+
it('excludes non-conventional commits converted to chore type', () => {
123+
const commits = filterCommits([
124+
{
125+
type: 'chore',
126+
notes: [],
127+
references: [],
128+
bareMessage: 'update dependencies',
129+
message: 'update dependencies',
130+
scope: null,
131+
breaking: false,
132+
sha: 'ghi789',
133+
},
134+
]);
135+
expect(commits.length).to.equal(0);
136+
});
92137
});

0 commit comments

Comments
 (0)