@@ -11,6 +11,7 @@ import {
1111 applyBump ,
1212 calculateVersion ,
1313 findReleaseAs ,
14+ hasBreakingChangeBody ,
1415 hasNonTestChanges ,
1516 parseSemver ,
1617} from "./version.ts" ;
@@ -45,6 +46,7 @@ describe("analyzeCommits", () => {
4546
4647 it ( "returns 'major' for breaking change with ! suffix" , ( ) => {
4748 assertEquals ( analyzeCommits ( [ "feat!: breaking change" ] ) , "major" ) ;
49+ assertEquals ( analyzeCommits ( [ "fix!: breaking fix" ] ) , "major" ) ;
4850 } ) ;
4951
5052 it ( "returns 'major' for breaking change with BREAKING CHANGE in subject" , ( ) => {
@@ -61,6 +63,15 @@ describe("analyzeCommits", () => {
6163 ) ;
6264 } ) ;
6365
66+ it ( "returns 'major' when a commit body contains BREAKING CHANGE" , ( ) => {
67+ assertEquals (
68+ analyzeCommits ( [ "feat: keep subject normal" ] , [
69+ "BREAKING CHANGE: api changed" ,
70+ ] ) ,
71+ "major" ,
72+ ) ;
73+ } ) ;
74+
6475 it ( "returns highest bump when mixed commits (feat + fix → minor)" , ( ) => {
6576 const subjects = [
6677 "fix: bug fix" ,
@@ -116,6 +127,24 @@ describe("analyzeCommits", () => {
116127 } ) ;
117128} ) ;
118129
130+ describe ( "hasBreakingChangeBody" , ( ) => {
131+ it ( "returns true for semantic-release style breaking change bodies" , ( ) => {
132+ assertEquals (
133+ hasBreakingChangeBody ( [
134+ "Some text\n\nBREAKING CHANGE: changed output format" ,
135+ ] ) ,
136+ true ,
137+ ) ;
138+ } ) ;
139+
140+ it ( "returns false when commit bodies do not include the breaking footer" , ( ) => {
141+ assertEquals (
142+ hasBreakingChangeBody ( [ "Regular body" , "Another body" ] ) ,
143+ false ,
144+ ) ;
145+ } ) ;
146+ } ) ;
147+
119148describe ( "findReleaseAs" , ( ) => {
120149 it ( "returns undefined for empty array" , ( ) => {
121150 assertEquals ( findReleaseAs ( [ ] ) , undefined ) ;
@@ -349,6 +378,16 @@ describe("calculateVersion", () => {
349378 assertEquals ( result , { skip : false , version : "2.0.0" , tag : "latest" } ) ;
350379 } ) ;
351380
381+ it ( "creates release version for BREAKING CHANGE in commit body" , ( ) => {
382+ const result = calculateVersion ( {
383+ ...baseOpts ,
384+ subjects : [ "feat: keep subject stable" ] ,
385+ bodies : [ "BREAKING CHANGE: api changed" ] ,
386+ eventName : "push" ,
387+ } ) ;
388+ assertEquals ( result , { skip : false , version : "2.0.0" , tag : "latest" } ) ;
389+ } ) ;
390+
352391 it ( "skips when no triggering commits" , ( ) => {
353392 const result = calculateVersion ( {
354393 ...baseOpts ,
@@ -468,6 +507,20 @@ describe("calculateVersion", () => {
468507 } ) ;
469508 } ) ;
470509
510+ it ( "creates canary for BREAKING CHANGE in commit body" , ( ) => {
511+ const result = calculateVersion ( {
512+ ...baseOpts ,
513+ subjects : [ "fix: preserve subject format" ] ,
514+ bodies : [ "BREAKING CHANGE: cache schema changed" ] ,
515+ eventName : "pull_request" ,
516+ } ) ;
517+ assertEquals ( result , {
518+ skip : false ,
519+ version : "2.0.0-canary.abc123d.20260212091429" ,
520+ tag : "canary" ,
521+ } ) ;
522+ } ) ;
523+
471524 it ( "shortens commit SHA to 7 characters" , ( ) => {
472525 const result = calculateVersion ( {
473526 ...baseOpts ,
@@ -618,5 +671,20 @@ describe("calculateVersion", () => {
618671 tag : "canary" ,
619672 } ) ;
620673 } ) ;
674+
675+ it ( "creates a 0.x canary minor bump from BREAKING CHANGE in body" , ( ) => {
676+ const result = calculateVersion ( {
677+ ...baseOpts ,
678+ currentVersion : "0.1.12" ,
679+ subjects : [ "feat: keep subject stable" ] ,
680+ bodies : [ "BREAKING CHANGE: overhaul session-memory semantics" ] ,
681+ eventName : "pull_request" ,
682+ } ) ;
683+ assertEquals ( result , {
684+ skip : false ,
685+ version : "0.2.0-canary.abc123d.20260212091429" ,
686+ tag : "canary" ,
687+ } ) ;
688+ } ) ;
621689 } ) ;
622690} ) ;
0 commit comments