Skip to content
256 changes: 145 additions & 111 deletions engine.js
Original file line number Diff line number Diff line change
Expand Up @@ -40,7 +40,7 @@ module.exports = function(options) {
var types = options.types;

var length = longest(Object.keys(types)).length + 1;
var choices = map(types, function(type, key) {
var typeChoices = map(types, function(type, key) {
return {
name: (key + ':').padEnd(length) + ' ' + type.description,
value: key
Expand Down Expand Up @@ -72,122 +72,156 @@ module.exports = function(options) {
type: 'list',
name: 'type',
message: "Select the type of change that you're committing:",
choices: choices,
choices: typeChoices,
default: options.defaultType
},
{
type: 'input',
name: 'scope',
message:
'What is the scope of this change (e.g. component or file name): (press enter to skip)',
default: options.defaultScope,
filter: function(value) {
}
])
.then(function(firstAnswers) {
var scopes = options.allowedScopes;

var scopeChoices = scopes && scopes.length ?
[{
name: 'Empty',
value: undefined
}].concat(map(scopes, function(scope) {
return {
name: scope,
value: scope
};
}))
: null;
var filterScope = function(value) {
if (!value) {
return value;
}

return options.disableScopeLowerCase
? value.trim()
: value.trim().toLowerCase();
}
},
{
type: 'input',
name: 'subject',
message: function(answers) {
return (
'Write a short, imperative tense description of the change (max ' +
maxSummaryLength(options, answers) +
' chars):\n'
);
},
default: options.defaultSubject,
validate: function(subject, answers) {
var filteredSubject = filterSubject(subject, options.disableSubjectLowerCase);
return filteredSubject.length == 0
? 'subject is required'
: filteredSubject.length <= maxSummaryLength(options, answers)
? true
: 'Subject length must be less than or equal to ' +
maxSummaryLength(options, answers) +
' characters. Current length is ' +
filteredSubject.length +
' characters.';
},
transformer: function(subject, answers) {
var filteredSubject = filterSubject(subject, options.disableSubjectLowerCase);
var color =
filteredSubject.length <= maxSummaryLength(options, answers)
? chalk.green
: chalk.red;
return color('(' + filteredSubject.length + ') ' + subject);
},
filter: function(subject) {
return filterSubject(subject, options.disableSubjectLowerCase);
}
},
{
type: 'input',
name: 'body',
message:
'Provide a longer description of the change: (press enter to skip)\n',
default: options.defaultBody
},
{
type: 'confirm',
name: 'isBreaking',
message: 'Are there any breaking changes?',
default: false
},
{
type: 'input',
name: 'breakingBody',
default: '-',
message:
'A BREAKING CHANGE commit requires a body. Please enter a longer description of the commit itself:\n',
when: function(answers) {
return answers.isBreaking && !answers.body;
},
validate: function(breakingBody, answers) {
return (
breakingBody.trim().length > 0 ||
'Body is required for BREAKING CHANGE'
);
}
},
{
type: 'input',
name: 'breaking',
message: 'Describe the breaking changes:\n',
when: function(answers) {
return answers.isBreaking;
}
},

{
type: 'confirm',
name: 'isIssueAffected',
message: 'Does this change affect any open issues?',
default: options.defaultIssues ? true : false
},
{
type: 'input',
name: 'issuesBody',
default: '-',
message:
'If issues are closed, the commit requires a body. Please enter a longer description of the commit itself:\n',
when: function(answers) {
return (
answers.isIssueAffected && !answers.body && !answers.breakingBody
);
}
},
{
type: 'input',
name: 'issues',
message: 'Add issue references (e.g. "fix #123", "re #123".):\n',
when: function(answers) {
return answers.isIssueAffected;
},
default: options.defaultIssues ? options.defaultIssues : undefined
}
]).then(function(answers) {
var scopePrompt = scopeChoices ? {
type: 'list',
name: 'type',
message: "Select the scope of change that you're committing:",
choices: scopeChoices,
default: options.defaultScope,
filter: filterScope
} : {
type: 'input',
name: 'scope',
message:
'What is the scope of this change (e.g. component or file name): (press enter to skip)',
default: options.defaultScope,
filter: filterScope
};

return cz.prompt([
scopePrompt,
{
type: 'input',
name: 'subject',
message: function(answers) {
return (
'Write a short, imperative tense description of the change (max ' +
maxSummaryLength(options, answers) +
' chars):\n'
);
},
default: options.defaultSubject,
validate: function(subject, answers) {
var filteredSubject = filterSubject(subject, options.disableSubjectLowerCase);
return filteredSubject.length == 0
? 'subject is required'
: filteredSubject.length <= maxSummaryLength(options, answers)
? true
: 'Subject length must be less than or equal to ' +
maxSummaryLength(options, answers) +
' characters. Current length is ' +
filteredSubject.length +
' characters.';
},
transformer: function(subject, answers) {
var filteredSubject = filterSubject(subject, options.disableSubjectLowerCase);
var color =
filteredSubject.length <= maxSummaryLength(options, answers)
? chalk.green
: chalk.red;
return color('(' + filteredSubject.length + ') ' + subject);
},
filter: function(subject) {
return filterSubject(subject, options.disableSubjectLowerCase);
}
},
{
type: 'input',
name: 'body',
message:
'Provide a longer description of the change: (press enter to skip)\n',
default: options.defaultBody
},
{
type: 'confirm',
name: 'isBreaking',
message: 'Are there any breaking changes?',
default: false
},
{
type: 'input',
name: 'breakingBody',
default: '-',
message:
'A BREAKING CHANGE commit requires a body. Please enter a longer description of the commit itself:\n',
when: function(answers) {
return answers.isBreaking && !answers.body;
},
validate: function(breakingBody, answers) {
return (
breakingBody.trim().length > 0 ||
'Body is required for BREAKING CHANGE'
);
}
},
{
type: 'input',
name: 'breaking',
message: 'Describe the breaking changes:\n',
when: function(answers) {
return answers.isBreaking;
}
},

{
type: 'confirm',
name: 'isIssueAffected',
message: 'Does this change affect any open issues?',
default: options.defaultIssues ? true : false
},
{
type: 'input',
name: 'issuesBody',
default: '-',
message:
'If issues are closed, the commit requires a body. Please enter a longer description of the commit itself:\n',
when: function(answers) {
return (
answers.isIssueAffected && !answers.body && !answers.breakingBody
);
}
},
{
type: 'input',
name: 'issues',
message: 'Add issue references (e.g. "fix #123", "re #123".):\n',
when: function(answers) {
return answers.isIssueAffected;
},
default: options.defaultIssues ? options.defaultIssues : undefined
}
]).then(function(restOfAnswers) {
return Object.assign(firstAnswers, restOfAnswers);
});
}).then(function(answers) {
var wrapOptions = {
trim: true,
cut: false,
Expand Down
99 changes: 97 additions & 2 deletions engine.test.js
Original file line number Diff line number Diff line change
Expand Up @@ -480,6 +480,88 @@ describe('commitlint config header-max-length', function() {
});
}
});

describe('commitlint config scope-enum', function() {
//commitlint config parser only supports Node 6.0.0 and higher
if (semver.gte(process.version, '6.0.0')) {
function mockOptions(allowedScopes) {
var options = undefined;
mock('./engine', function(opts) {
options = opts;
});
if (allowedScopes) {
mock('cosmiconfig', function() {
return {
load: function(cwd) {
return {
filepath: cwd + '/.commitlintrc.js',
config: {
rules: {
'scope-enum': [2, 'always', allowedScopes]
}
}
};
}
};
});
}

mock.reRequire('./index');
try {
return mock
.reRequire('@commitlint/load')()
.then(function() {
return options;
});
} catch (err) {
return Promise.resolve(options);
}
}

afterEach(function() {
delete require.cache[require.resolve('./index')];
delete require.cache[require.resolve('@commitlint/load')];
delete process.env.CZ_ALLOWED_SCOPES;
mock.stopAll();
});

it('with no environment or commitizen config override', function() {
return mockOptions(['client', 'server']).then(function(options) {
expect(options).to.have.deep.property('allowedScopes', ['client', 'server']);
});
});

it('with environment variable override', function() {
process.env.CZ_ALLOWED_SCOPES = 'other,scopes';
return mockOptions(['client', 'server']).then(function(options) {
expect(options).to.have.deep.property('allowedScopes', ['other', 'scopes']);
});
});

it('with commitizen config override', function() {
mock('commitizen', {
configLoader: {
load: function() {
return {
allowedScopes: ['other', 'scopes']
};
}
}
});
return mockOptions(['client', 'server']).then(function(options) {
expect(options).to.have.deep.property('allowedScopes', ['other', 'scopes']);
});
});
} else {
//Node 4 doesn't support commitlint so the config value should remain the same
it('default value for Node 4', function() {
return mockOptions(['other', 'scopes']).then(function(options) {
expect(options).to.have.deep.property('allowedScopes', []);
});
});
}
});

function commitMessage(answers, options) {
options = options || defaultOptions;
var result = null;
Expand All @@ -490,6 +572,13 @@ function commitMessage(answers, options) {
then: function(finalizer) {
processQuestions(questions, answers, options);
finalizer(answers);

return {
then: function(finalizer) {
processQuestions(questions, answers, options);
finalizer(answers);
}
};
}
};
}
Expand Down Expand Up @@ -526,9 +615,15 @@ function getQuestions(options) {
var result = null;
engine(options).prompter({
prompt: function(questions) {
result = questions;
result = result ? result.concat(questions) : questions;
return {
then: function() {}
then: function(secondRun) {
secondRun({});

return {
then: function() {}
};
}
};
}
});
Expand Down
Loading