parserPreset is overwritten when extending multiple configurations
See original GitHub issueI am attempting to replace the default headerPattern and headerCorrespondence values of @commitlint/config-conventional by using a plugin module.
My plugin module looks like this:
index.js
module.exports = {
parserPreset: {
parserOpts: {
headerPattern: /^(?:([A-Z]{2,}-\d+(?:, [A-Z]{2,}-\d+)*) \| ){1}(\w*)(?:\((\w*)\))?!?: (.+)$/,
headerCorrespondence: [`jiraIds`, `type`, `scope`, `subject`],
},
},
rules: {
'header-max-length': [2, `always`, 110],
'jira-empty': [2, `always`],
},
plugins: [
{
rules: {
'jira-empty': ({ jiraIds }) => {
const test = jiraIds ? true : false;
return [
test,
`jira id(s) may not be empty`,
]
},
}
}
]
}
package.json
{
"name": "@acse/commitlint-config",
"version": "0.4.0",
"description": "ACSE Commitlint Config",
"files": [
"index.js"
],
"keywords": [
"acse",
"commitlint"
],
"peerDependencies": {
"@commitlint/cli": "^17.0.3",
"@commitlint/config-conventional": "^17.0.3"
}
}
Expected Behavior
From a test git repository with @commitlint/cli
, @commitlint/config-conventional
, and @acse/commitlint-config
(the plugin module) installed locally, run:
echo 'JIRA-111, JIRA-222 | feat(scope): subject' | ./node_modules/.bin/commitlint -x @commitlint/config-conventional @acse/commitlint-config
My expectation would be that this command exits in success (since that header line passes the headerPattern regex set in the plugin module’s config.
Current Behavior
Instead that command exits in failure because the parserPreset
in the resolved config has been overwritten by the parserPreset that @commitlint/config-conventional
sets (note the addition of --print-config
):
# echo 'JIRA-111, JIRA-2222 | feat(scope): subject' | ./node_modules/.bin/commitlint -x @commitlint/config-conventional @acse/commitlint-config --print-config
{
extends: [ '@commitlint/config-conventional', '@acse/commitlint-config' ],
formatter: '/home/voxmachina/dev/test/test/node_modules/@commitlint/format/lib/index.js',
parserPreset: {
parserOpts: {
headerPattern: /^(\w*)(?:\((.*)\))?!?: (.*)$/,
breakingHeaderPattern: /^(\w*)(?:\((.*)\))?!: (.*)$/,
headerCorrespondence: [ 'type', 'scope', 'subject' ],
noteKeywords: [ 'BREAKING CHANGE', 'BREAKING-CHANGE' ],
revertPattern: /^(?:Revert|revert:)\s"?([\s\S]+?)"?\s*This reverts commit (\w*)\./i,
revertCorrespondence: [ 'header', 'hash' ],
issuePrefixes: [ '#' ]
},
name: 'conventional-changelog-conventionalcommits',
path: './node_modules/conventional-changelog-conventionalcommits/index.js'
},
ignores: undefined,
defaultIgnores: undefined,
plugins: { local: { rules: { 'jira-empty': [Function: jira-empty] } } },
rules: {
'body-leading-blank': [ 1, 'always' ],
'body-max-line-length': [ 2, 'always', 100 ],
'footer-leading-blank': [ 1, 'always' ],
'footer-max-line-length': [ 2, 'always', 100 ],
'header-max-length': [ 2, 'always', 110 ],
'subject-case': [
2,
'never',
[ 'sentence-case', 'start-case', 'pascal-case', 'upper-case' ]
],
'subject-empty': [ 2, 'never' ],
'subject-full-stop': [ 2, 'never', '.' ],
'type-case': [ 2, 'always', 'lower-case' ],
'type-empty': [ 2, 'never' ],
'type-enum': [
2,
'always',
[
'build', 'chore',
'ci', 'docs',
'feat', 'fix',
'perf', 'refactor',
'revert', 'style',
'test'
]
],
'jira-empty': [ 2, 'always' ]
},
helpUrl: 'https://github.com/conventional-changelog/commitlint/#what-is-commitlint',
prompt: {
questions: {
type: {
description: "Select the type of change that you're committing",
enum: {
feat: {
description: 'A new feature',
title: 'Features',
emoji: '✨'
},
fix: { description: 'A bug fix', title: 'Bug Fixes', emoji: '🐛' },
docs: {
description: 'Documentation only changes',
title: 'Documentation',
emoji: '📚'
},
style: {
description: 'Changes that do not affect the meaning of the code (white-space, formatting, missing semi-colons, etc)',
title: 'Styles',
emoji: '💎'
},
refactor: {
description: 'A code change that neither fixes a bug nor adds a feature',
title: 'Code Refactoring',
emoji: '📦'
},
perf: {
description: 'A code change that improves performance',
title: 'Performance Improvements',
emoji: '🚀'
},
test: {
description: 'Adding missing tests or correcting existing tests',
title: 'Tests',
emoji: '🚨'
},
build: {
description: 'Changes that affect the build system or external dependencies (example scopes: gulp, broccoli, npm)',
title: 'Builds',
emoji: '🛠'
},
ci: {
description: 'Changes to our CI configuration files and scripts (example scopes: Travis, Circle, BrowserStack, SauceLabs)',
title: 'Continuous Integrations',
emoji: '⚙️'
},
chore: {
description: "Other changes that don't modify src or test files",
title: 'Chores',
emoji: '♻️'
},
revert: {
description: 'Reverts a previous commit',
title: 'Reverts',
emoji: '🗑'
}
}
},
scope: {
description: 'What is the scope of this change (e.g. component or file name)'
},
subject: {
description: 'Write a short, imperative tense description of the change'
},
body: { description: 'Provide a longer description of the change' },
isBreaking: { description: 'Are there any breaking changes?' },
breakingBody: {
description: 'A BREAKING CHANGE commit requires a body. Please enter a longer description of the commit itself'
},
breaking: { description: 'Describe the breaking changes' },
isIssueAffected: { description: 'Does this change affect any open issues?' },
issuesBody: {
description: 'If issues are closed, the commit requires a body. Please enter a longer description of the commit itself'
},
issues: {
description: 'Add issue references (e.g. "fix #123", "re #123".)'
}
}
}
}
You can see that it does correctly merge the local plugin definition and additional rule from the plugin module, but it overwrites the parserPreset
value entirely.
If I don’t additionally extend the configuration with with @commitlint/config-conventional
I do get the correct headerPattern and headerCorrespondence values, but then I lose everything else extending @commitlint/config-conventional
gives me (all the default rules, prompt definitions, etc):
# echo 'JIRA-111, JIRA-222 | feat(scope): subject' | ./node_modules/.bin/commitlint -x @acse/commitlint-config --print-config
{
extends: [ '@acse/commitlint-config' ],
formatter: '/home/voxmachina/dev/test/test/node_modules/@commitlint/format/lib/index.js',
parserPreset: {
parserOpts: {
headerPattern: /^(?:([A-Z]{2,}-\d+(?:, [A-Z]{2,}-\d+)*) \| ){1}(\w*)(?:\((\w*)\))?!?: (.+)$/,
headerCorrespondence: [ 'jiraIds', 'type', 'scope', 'subject' ]
}
},
ignores: undefined,
defaultIgnores: undefined,
plugins: { local: { rules: { 'jira-empty': [Function: jira-empty] } } },
rules: {
'header-max-length': [ 2, 'always', 110 ],
'jira-empty': [ 2, 'always' ]
},
helpUrl: 'https://github.com/conventional-changelog/commitlint/#what-is-commitlint',
prompt: {}
}
Affected packages
- cli
- config-conventional
Possible Solution
Not sure, but my expectation based on reading the documentation and gut instinct would be that the parserPreset
options are merged into the ones provided by packages extended “before” my plugin package (when defining the extensions via the -x
option on the commitlint
cli, i.e.:
Steps to Reproduce (for bugs)
- Create plugin module as detailed above
- Create test git repository that installs
@commitlint/cli
,@commitlint/config-conventional
, and<plugin_module>
locally - Run the above commands, validating that the
parserPreset
gets overwritten
Context
Essentially I am attempting to “extend” the default rules and configuration of the @commitlint/config-conventional
module with headerPattern
and headerCorrespondence
values that should inform the parser to parse out a superset of values that are expected from the header (jiraIds, type, scope, subject).
Your Environment
Executable | Version |
---|---|
commitlint --version |
17.0.3 |
git --version |
2.25.1 |
node --version |
16.14.2 |
Issue Analytics
- State:
- Created a year ago
- Comments:5
Top GitHub Comments
I am finding this impossible. With the comment above the parseOpts ends up ignored as it’s not within a parserPreset.
It seems the
@commitlint/resolve-extends
is unable to merge parserOpts because when you are referencing @commitlint/config-conventional it’s now a function that is returned for parserOpts, which of course can’t merge so it just overwrites.I was able to find a solution! I’m not sure what the technical mechanism was that was producing the issue but the fix was to factor out the
parserPreset
object into a separate file, i.e.:instead of:
do this:
and in parser-preset.js
Notes from Debugging:
@commitlint/cli
and@commitlint/load
leads me to believe that the issue lies somewhere in the interaction of these two components, but I was unable to track down exactly what was happening before finding the fix