conventional-changelog/conventional-changelog

Using Exclamation Mark for Breaking Change Confuses Parser

shellscape opened this issue · 10 comments

It appears that the conventional-commits-parser package doesn't directly support the exclamation mark demarcation for noting a breaking change in a commit. Parsing the following commit measage:

chore!: drop node 8 support

Will yield a node with the following structure:

{
  type: null,
  scope: null,
  subject: null,
  merge: null,
  header: 'chore!: drop node 8 support',
  body: null,
  footer: null,
  notes: [],
  references: [],
  mentions: [],
  revert: null,
  hash: 'd0059c44727bd0562db42aedeed60713b698c264',
  breaking: false
}

A notable number of properties are left null in addition to not recognizing the ! indicating a breaking change. Support for ! is noted in the first item in the Specification https://www.conventionalcommits.org/en/v1.0.0/#specification.

Ran into the same issue. Nothing ends up in changelog when using exclamation mark.

+1. We are having to use BREAKING CHANGE: in the footer of the commit message for now. But this should be supported as it is part of the conventional commits specification as mentioned above

fhenz commented

I solved this by supplying the parser function with the second options parameter.
The options argument includes the fields
headerPattern: /^(\w*)(?:\((.*)\))?!?: (.*)$/ and
breakingHeaderPattern: /^(\w*)(?:\((.*)\))?!: (.*)$/
which seemingly solved the issue.
I got these options from the "conventional-changelog-conventionalcommits" package, i.e.

const parser = require("conventional-commits-parser");
const spec = require("conventional-changelog-conventionalcommits");
...
const options = await spec();
const parsed = parser.sync(COMMIT, options.parserOpts);

resulting with COMMIT as chore!: drop node 8 support in this:

{
  type: 'chore',
  scope: null,
  subject: 'drop node 8 support',
  merge: null,
  header: 'chore!: drop node 8 support',
  body: null,
  footer: null,
  notes: [ { title: 'BREAKING CHANGE', text: 'drop node 8 support' } ],
  references: [],
  mentions: [],
  revert: null
}

Any update to have the fix merged?

Got caught with this today and made a botched release because of it :(

In our case we solved by overwriting conventional-changelog-conventionalcommits config
in the following way:
note the *! type

'use strict'
const config = require('conventional-changelog-conventionalcommits');

module.exports = config({
    "types": [
        { type: 'feat', section: 'New Features' },
        { type: 'fix', section: 'Bugs' },
        { type: '*!', section: 'BREAKING CHANGES' }
    ]
})

I tried both @tfn220189 's suggestion AND @fhenz's suggestion. Still no luck. Oddly, the changelog writer is getting the 'BREAKING CHANGE' note, but the commit analyzer still thinks feat!: something is a minor change, not a major one. Here's my entire release.config.js file:

const typeTransforms = [
    ['feat', 'Features'],
    ['fix', 'Bug Fixes'],
    ['perf', 'Performance'],
    ['revert', 'Reverts'],
    ['docs', 'Documentation'],
    ['style', 'Code Style'],
    ['refactor', 'Code Refactoring'],
    ['chore', 'Chores'],
    ['test', 'Tests'],
    ['build', 'Build'],
    ['ci', 'Continuous Integration'],
    ['*!', 'BREAKING CHANGES'],
];

const parserOpts = {
    "noteKeywords": ["BREAKING-CHANGE", "BREAKING CHANGE", "BREAKING CHANGES"],
    "headerPattern": /^(\w*)(?:\(([\w\$\.\-\* ]*)\))?!?: (.*)$/,
    "breakingHeaderPattern": /^(\w*)(?:\((.*)\))?!: (.*)$/,
}

// the following is copied and modified from https://github.com/conventional-changelog/conventional-changelog/blob/master/packages/conventional-changelog-angular/writer-opts.js
// eslint-disable-next-line max-lines-per-function
function transform(commit, context) {

    const issues = [];

    commit.notes.forEach((note) => {

        note.title = 'BREAKING CHANGES';

    });

    typeTransforms.forEach((type) => {

        if (commit.type === type[0]) {

            commit.type = type[1];

        }

    });

    if (commit.scope === '*') {

        commit.scope = '';

    }

    if (typeof commit.hash === 'string') {

        commit.shortHash = commit.hash.substring(0, 7);

    }

    if (typeof commit.subject === 'string') {

        let url = context.repository
            ? `${context.host}/${context.owner}/${context.repository}`
            : context.repoUrl;

        if (url) {

            url = `${url}/issues/`;
            // Issue URLs.
            commit.subject = commit.subject.replace(/#([0-9]+)/g, (_, issue) => {

                issues.push(issue);
                return `[#${issue}](${url}${issue})`;

            });

        }
        if (context.host) {

            // User URLs.
            commit.subject = commit.subject.replace(/\B@([a-z0-9](?:-?[a-z0-9/]){0,38})/g, (_, username) => {

                if (username.includes('/')) {

                    return `@${username}`;

                }

                return `[@${username}](${context.host}/${username})`;

            });

        }

    }

    // remove references that already appear in the subject
    commit.references = commit.references.filter((reference) => issues.indexOf(reference.issue) === -1);

    return commit;

}

module.exports = {
    plugins: [
        ['@semantic-release/commit-analyzer', {
            releaseRules: [
                {type: 'docs', release: 'patch'},
                {type: 'feat', release: 'minor'},
                {type: 'test', release: 'patch'},
                {type: 'chore', release: 'patch'},
                {type: 'perf', release: 'patch'},
                {type: 'style', release: 'patch'},
                {type: 'ci', release: 'patch'},
                {type: 'refactor', release: 'patch'},
                {type: 'build', release: 'patch'},
                {type: 'fix', release: 'patch'},
                {type: 'revert', release: 'patch'},
            ],
            parserOpts,
        }],
        ['@semantic-release/release-notes-generator', {
            writerOpts: {transform},
            parserOpts,
        }],
        '@semantic-release/npm',
        '@semantic-release/github',
    ],
};

We ended up giving up on this package and are using the parser directly. Over a year and no one from the team has addressed this, so I wouldn't hold out any hope for it being fixed. Here's the script that we use in place of conventional-changelog which does our changelog generation for packages in the Rollup plugins repo: https://github.com/rollup/plugins/blob/master/scripts/release.ts

Hi guys, I just made a commitizen cli. You can try running the command npx czg break in any of your projects.

demo:

demo

guide | website:

https://cz-git.qbb.sh/recipes/breakingchange.html
hope it can help u

Exclamation works perfectly with conventionalcommits preset:

npx conventional-recommended-bump --preset conventionalcommits

@carloschneider that's not the context of this issue