Skip to content
Merged
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
16 changes: 16 additions & 0 deletions .github/workflows/tests.yml
Original file line number Diff line number Diff line change
@@ -0,0 +1,16 @@
name: Tests
on:
push:
branches: [master]
pull_request:
branches: [master]
jobs:
parser:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v4
- uses: Ortus-Solutions/setup-commandbox@v2.0.1
- run: box install
- run: |
box server start --noBrowser
box testbox run
2 changes: 2 additions & 0 deletions .gitignore
Original file line number Diff line number Diff line change
@@ -1,2 +1,4 @@
/modules/
jmimemagic.log
/testbox/
/.engine/
12 changes: 10 additions & 2 deletions box.json
Original file line number Diff line number Diff line change
Expand Up @@ -26,14 +26,22 @@
"ignore":[
"**/.*",
"test",
"tests"
"tests",
"server.json"
],
"dependencies":{
"semver":"^1.1.3",
"hyper":"^3.6.2"
},
"installPaths":{
"semver":"modules/semver/",
"hyper":"modules/hyper/"
"hyper":"modules/hyper/",
"testbox":"testbox/"
},
"devDependencies":{
"testbox":"^7.0.0+19"
},
"testbox":{
"runner":"http://127.0.0.1:60359/tests/runner.cfm"
}
}
47 changes: 31 additions & 16 deletions models/plugins/ConventionalChangelogParser.cfc
Original file line number Diff line number Diff line change
Expand Up @@ -35,22 +35,7 @@ component implements="interfaces.CommitParser" {
boolean dryRun = false,
boolean verbose = false
) {
var ccCommit = {};
var parts = arraySlice( replace( commit.getFullMessage(), chr( 13 ), "", "all" ).split("\n{2,}"), 1 );
var topParts = reFindNoCase( "^(\w+)\(([^)]+)\)\:\s(.+)$", parts[ 1 ], 1, true );

ccCommit[ "type" ] = topParts.pos.len() >= 2 ?
mid( parts[ 1 ], topParts.pos[ 2 ], topParts.len[ 2 ] ) :
"other";
ccCommit[ "scope" ] = topParts.pos.len() >= 3 ?
mid( parts[ 1 ], topParts.pos[ 3 ], topParts.len[ 3 ] ) :
"*";
ccCommit[ "subject" ] = topParts.pos.len() >= 4 ?
mid( parts[ 1 ], topParts.pos[ 4 ], topParts.len[ 4 ] ) :
"";
ccCommit[ "body" ] = topParts.pos.len() == 1 ? parts[ 1 ] : parts[ 2 ] ?: "";
ccCommit[ "footer" ] = parts[ 3 ] ?: "";
ccCommit[ "isBreakingChange" ] = find( "BREAKING CHANGE:", ccCommit.footer ) > 0;
var ccCommit = parseMessage( commit.getFullMessage() );
ccCommit[ "hash" ] = commit.getId().getName();
ccCommit[ "shortHash" ] = objectReader.abbreviate( commit.getId() ).name();

Expand All @@ -61,6 +46,36 @@ component implements="interfaces.CommitParser" {
return ccCommit;
}

/** Extract conventional header, body, and breaking footer from any standard paragraph layout. */
public struct function parseMessage( required string message ) {
var ccCommit = {};
var parts = listToArray( reReplace( replace( message, chr( 13 ), "", "all" ), "\n{2,}", chr( 1 ), "all" ), chr( 1 ), true );
var header = parts[ 1 ];
var topParts = reFindNoCase( "^(\w+)(?:\(([^)]+)\))?(!)?\:\s(.+)$", header, 1, true );

ccCommit.type = arrayLen( topParts.pos ) >= 2 ? lCase( mid( header, topParts.pos[ 2 ], topParts.len[ 2 ] ) ) : "other";
ccCommit.scope = arrayLen( topParts.pos ) >= 3 && topParts.len[ 3 ] > 0 ?
mid( header, topParts.pos[ 3 ], topParts.len[ 3 ] ) : "*";
ccCommit.subject = arrayLen( topParts.pos ) >= 5 ? mid( header, topParts.pos[ 5 ], topParts.len[ 5 ] ) : "";
ccCommit.body = "";
ccCommit.footer = "";
ccCommit.isBreakingChange = arrayLen( topParts.pos ) >= 4 && topParts.len[ 4 ] > 0;

var i = 0;
for ( var paragraph in parts ) {
i++;
if ( i == 1 ) continue;
if ( reFindNoCase( "(?m)^BREAKING(?: CHANGE|-CHANGE):\s*\S", paragraph ) ) {
ccCommit.footer = arrayToList( arraySlice( parts, i ), chr( 10 ) & chr( 10 ) );
ccCommit.isBreakingChange = true;
break;
}
ccCommit.body &= ( len( ccCommit.body ) ? chr( 10 ) & chr( 10 ) : "" ) & paragraph;
}

return ccCommit;
}

/**
* Print a parsed commit in a nice format to the console.
*
Expand Down
14 changes: 14 additions & 0 deletions server.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,14 @@
{
"name":"semantic-release-breaking-tests",
"app":{
"cfengine":"lucee@6",
"serverHomeDirectory":".engine/breaking-tests"
},
"web":{
"http":{
"port":60359
}
},
"openBrowser":false,
"noBrowser":true
}
6 changes: 6 additions & 0 deletions tests/Application.cfc
Original file line number Diff line number Diff line change
@@ -0,0 +1,6 @@
component {
this.name = "semantic-release-tests";
this.mappings[ "/semanticRelease" ] = getDirectoryFromPath( getCurrentTemplatePath() ) & "../";
this.mappings[ "/testbox" ] = this.mappings[ "/semanticRelease" ] & "testbox";
this.mappings[ "/semver" ] = this.mappings[ "/semanticRelease" ] & "modules/semver";
}
5 changes: 5 additions & 0 deletions tests/runner.cfm
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
<cfsetting showDebugOutput="false">
<cfparam name="url.reporter" default="simple">
<cfparam name="url.directory" default="tests.specs">
<cfparam name="url.recurse" default="true">
<cfinclude template="/testbox/system/runners/HTMLRunner.cfm">
42 changes: 42 additions & 0 deletions tests/specs/CommitParsingSpec.cfc
Original file line number Diff line number Diff line change
@@ -0,0 +1,42 @@
component extends="testbox.system.BaseSpec" {
function run() {
describe( "breaking release classification", function() {
beforeEach( function() {
variables.parser = new semanticRelease.models.plugins.ConventionalChangelogParser();
variables.analyzer = new semanticRelease.models.plugins.DefaultCommitAnalyzer();
} );

it( "recognizes qb's breaking commits with a footer directly after the subject", function() {
var messages = [
"feat(QueryBuilder): match null upsert targets (issue 324)" & chr( 10 ) & chr( 10 ) &
"BREAKING CHANGE: Custom grammars overriding compileUpsert must accept matchNulls.",
"feat(QueryBuilder): add named return formatters (issue 315)" & chr( 10 ) & chr( 10 ) &
"BREAKING CHANGE: Native queryExecute returntype options are no longer honored."
];
var commits = messages.map( function( message ) { return parser.parseMessage( message ); } );
expect( commits[ 1 ].scope ).toBe( "QueryBuilder" );
expect( commits[ 1 ].subject ).toBe( "match null upsert targets (issue 324)" );
expect( commits[ 1 ].footer ).toInclude( "BREAKING CHANGE:" );
expect( analyzer.run( commits ) ).toBe( "major" );
} );

it( "finds a footer after multiple body paragraphs", function() {
var commit = parser.parseMessage( "fix(qb): change behavior" & chr( 10 ) & chr( 10 ) &
"First explanation." & chr( 10 ) & chr( 10 ) & "Second explanation." & chr( 10 ) & chr( 10 ) &
"BREAKING CHANGE: Consumers must adapt." );
expect( commit.body ).toInclude( "Second explanation." );
expect( commit.footer ).toInclude( "Consumers must adapt." );
expect( analyzer.run( [ commit ] ) ).toBe( "major" );
} );

it( "recognizes conventional exclamation headers but ignores a body mention", function() {
expect( analyzer.run( [ parser.parseMessage( "fix!: change behavior" ) ] ) ).toBe( "major" );
expect( analyzer.run( [ parser.parseMessage( "feat(qb)!: change behavior" ) ] ) ).toBe( "major" );
var commit = parser.parseMessage( "fix(qb): ordinary fix" & chr( 10 ) & chr( 10 ) &
"The docs mention BREAKING CHANGE: as an example." );
expect( commit.isBreakingChange ).toBeFalse();
expect( analyzer.run( [ commit ] ) ).toBe( "patch" );
} );
} );
}
}
Loading