diff --git a/lib/cli.js b/lib/cli.js index ecb979fd..5cfc9a90 100644 --- a/lib/cli.js +++ b/lib/cli.js @@ -1,8 +1,16 @@ -const {prompter} = require('.'); +const signale = require('signale'); +const createState = require('./createState'); +const runInteractiveQuestions = require('./runInteractiveQuestions'); -const cz = {}; -const onCommit = (data) => { - console.log('receibed for commit', data); +const main = async () => { + try { + const state = createState(); + const answers = await runInteractiveQuestions(state); + + console.log(answers); + } catch (error) { + signale.fatal(error); + } }; -prompter(cz, onCommit); +main(); diff --git a/lib/createPrompter.js b/lib/createPrompter.js index 6363ee22..1542fabb 100644 --- a/lib/createPrompter.js +++ b/lib/createPrompter.js @@ -1,23 +1,15 @@ const fs = require('fs'); -const inquirer = require('inquirer'); const wrap = require('word-wrap'); -const appRoot = require('app-root-path'); -const { - createPackagesQuestion, - createQuestions -} = require('./questions'); -const LimitedInputPrompt = require('./LimitedInputPrompt'); const { getAllPackages, getChangedPackages } = require('./lernaUtils'); +const runInteractiveQuestions = require('./runInteractiveQuestions'); const MAX_LINE_WIDTH = 72; -inquirer.registerPrompt('limitedInput', LimitedInputPrompt); - -const IS_LERNA_PROJECT = fs.existsSync(appRoot.resolve('lerna.json')); +/* const makeAffectsLine = function (answers) { const selectedPackages = answers.packages; @@ -27,55 +19,54 @@ const makeAffectsLine = function (answers) { return ''; }; +*/ -module.exports = (config) => { +module.exports = (state) => { const prompter = { - prompter (cz, commit) { - let promptQuestions = createQuestions(config); + async prompter (cz, commit) { + const answers = await runInteractiveQuestions(state); - if (IS_LERNA_PROJECT) { - const allPackages = getAllPackages().map((pkg) => pkg.name); - const changedPackages = getChangedPackages(); - promptQuestions = promptQuestions.concat(createPackagesQuestion(allPackages, changedPackages)); - } + // if (IS_LERNA_PROJECT) { + // const allPackages = getAllPackages().map((pkg) => pkg.name); + // const changedPackages = getChangedPackages(); + // + // promptQuestions = promptQuestions.concat(createPackagesQuestion(allPackages, changedPackages)); + // } - return inquirer.prompt(promptQuestions) - .then((answers) => { - const wrapOptions = { - indent: '', - trim: true, - width: MAX_LINE_WIDTH - }; + const wrapOptions = { + indent: '', + trim: true, + width: MAX_LINE_WIDTH + }; - const emoji = config.types[answers.type].emoji; - const emojiPrefix = emoji ? emoji + ' ' : ''; - const head = answers.type + ': ' + emojiPrefix + answers.subject; - const affectsLine = makeAffectsLine(answers); + const emoji = config.types[answers.type].emoji; + const emojiPrefix = emoji ? emoji + ' ' : ''; + const head = answers.type + ': ' + emojiPrefix + answers.subject; + const affectsLine = makeAffectsLine(answers); - // Wrap these lines at MAX_LINE_WIDTH character - const body = wrap(answers.body + affectsLine, wrapOptions); - const breaking = wrap(answers.breaking, wrapOptions); - const footer = wrap(answers.footer, wrapOptions); + // Wrap these lines at MAX_LINE_WIDTH character + const body = wrap(answers.body + affectsLine, wrapOptions); + const breaking = wrap(answers.breaking, wrapOptions); + const footer = wrap(answers.footer, wrapOptions); - let msg; + let msg; - msg = head; + msg = head; - if (body) { - msg += '\n\n' + body; - } + if (body) { + msg += '\n\n' + body; + } - if (breaking) { - msg += '\n\nBREAKING CHANGE: ' + breaking; - } + if (breaking) { + msg += '\n\nBREAKING CHANGE: ' + breaking; + } - if (footer) { - msg += '\n\nIssues: ' + footer; - } + if (footer) { + msg += '\n\nIssues: ' + footer; + } - return commit(msg); - }); + return commit(msg); } }; diff --git a/lib/createQuestions.js b/lib/createQuestions.js new file mode 100644 index 00000000..d51dc406 --- /dev/null +++ b/lib/createQuestions.js @@ -0,0 +1,9 @@ +/* eslint-disable import/no-dynamic-require, global-require */ + +const createQuestions = (state) => { + const questions = state.config.questions.map((name) => require('./questions/' + name).createQuestion(state)); + + return questions.filter(Boolean); +}; + +module.exports = createQuestions; diff --git a/lib/createState.js b/lib/createState.js new file mode 100644 index 00000000..90f9a5bb --- /dev/null +++ b/lib/createState.js @@ -0,0 +1,13 @@ +const appRoot = require('app-root-path'); +const getConfig = require('./getConfig'); + +const createState = () => { + const state = { + config: getConfig(), + root: String(appRoot) + }; + + return state; +}; + +module.exports = createState; diff --git a/lib/defaults.js b/lib/defaults.js index 78383bf6..b07c2401 100644 --- a/lib/defaults.js +++ b/lib/defaults.js @@ -61,7 +61,7 @@ const list = [ // https://github.com/angular/angular/blob/master/CONTRIBUTING.md#scope const scopes = [ - null, + '', 'init', 'runner', 'watcher', @@ -70,10 +70,21 @@ const scopes = [ 'changelog' ]; +const questions = [ + 'type', + 'scope', + 'title', + 'body', + 'breaking', + 'issues', + // 'lerna' +]; + module.exports = { list, maxMessageLength: 50, minMessageLength: 3, + questions, scopes, types }; diff --git a/lib/getConfig.js b/lib/getConfig.js index cc57c1bf..daf5ff17 100644 --- a/lib/getConfig.js +++ b/lib/getConfig.js @@ -1,9 +1,11 @@ /* eslint-disable global-require, import/no-dynamic-require */ const path = require('path'); -const defaults = require('./defaults'); +const fs = require('fs'); const signale = require('signale'); +const defaults = require('./defaults'); const configFiles = [ + '.git-cz.json', 'changelog.config.js', 'changelog.config.json' ]; @@ -12,20 +14,24 @@ const findOverrides = () => { const dir = process.cwd(); for (const file of configFiles) { - try { - return require(path.join(dir, file)); - // eslint-disable-next-line no-empty - } catch (error) {} + const filename = path.join(dir, file); + + if (fs.existsSync(filename)) { + return require(filename); + } } - try { - const changelog = require(path.join(dir, 'package.json')).config.commitizen.changelog; + const pkgFilename = path.join(dir, 'package.json'); + + if (fs.existsSync(pkgFilename)) { + const changelog = require(pkgFilename).config.commitizen.changelog; if (changelog) { return changelog; } - // eslint-disable-next-line no-empty - } catch (error) {} + } else { + signale.error('package.json not found, at ' + pkgFilename); + } return {}; }; diff --git a/lib/lernaUtils.js b/lib/lernaUtils.js index 290a8932..933586da 100755 --- a/lib/lernaUtils.js +++ b/lib/lernaUtils.js @@ -9,8 +9,6 @@ const getAllPackages = function () { }; const getChangedPackages = function () { - const shell = require('shelljs'); - const changedFiles = shell.exec('git diff --cached --name-only', {silent: true}) .stdout .split('\n'); diff --git a/lib/questions.js b/lib/questions.js deleted file mode 100644 index 569c46f6..00000000 --- a/lib/questions.js +++ /dev/null @@ -1,55 +0,0 @@ -/* eslint-disable global-require */ -const chalk = require('chalk'); - -const createQuestions = (config) => { - const MIN_SUBJECT_LENGTH_ERROR_MESSAGE = `The subject must have at least ${config.minMessageLength} characters`; - const questions = [ - require('./steps/type').createQuestion({}, config), - { - filter: (input) => { - let subject; - - subject = input.trim(); - while (subject.endsWith('.')) { - subject = subject.substr(0, subject.length - 1).trim(); - } - - return subject; - }, - leadingLabel: (answers) => `${answers.type}:`, - - // Minus 3 chars are for emoji + space. - maxLength: config.maxMessageLength - 3, - message: 'Write a short, imperative mood description of the change:', - name: 'subject', - type: 'limitedInput', - validate: (input) => input.length >= config.minMessageLength || MIN_SUBJECT_LENGTH_ERROR_MESSAGE - }, - { - message: 'Provide a longer description of the change:\n ', - name: 'body', - type: 'input' - }, - { - message: `List any breaking changes:\n ${chalk.red('BREAKING CHANGE')}:`, - name: 'breaking', - type: 'input' - }, - require('./steps/issues').createQuestion() - ]; - - return questions; -}; - -const createPackagesQuestion = (allPackages, changedPackages) => ({ - choices: allPackages, - default: changedPackages, - message: `The packages that this commit has affected (${changedPackages.length} detected)\n`, - name: 'packages', - type: 'checkbox' -}); - -module.exports = { - createPackagesQuestion, - createQuestions -}; diff --git a/lib/questions/body.js b/lib/questions/body.js new file mode 100644 index 00000000..c50720fd --- /dev/null +++ b/lib/questions/body.js @@ -0,0 +1,9 @@ +exports.createQuestion = () => { + const question = { + message: 'Provide a longer description of the change:\n ', + name: 'body', + type: 'input' + }; + + return question; +}; diff --git a/lib/questions/breaking.js b/lib/questions/breaking.js new file mode 100644 index 00000000..75f763c6 --- /dev/null +++ b/lib/questions/breaking.js @@ -0,0 +1,11 @@ +const chalk = require('chalk'); + +exports.createQuestion = () => { + const question = { + message: `List any breaking changes:\n ${chalk.red('BREAKING CHANGE')}:`, + name: 'breaking', + type: 'input' + }; + + return question; +}; diff --git a/lib/steps/issues.js b/lib/questions/issues.js similarity index 100% rename from lib/steps/issues.js rename to lib/questions/issues.js diff --git a/lib/questions/lerna.js b/lib/questions/lerna.js new file mode 100644 index 00000000..c241debb --- /dev/null +++ b/lib/questions/lerna.js @@ -0,0 +1,11 @@ +exports.createQuestion = (state) => { + const question = { + choices: allPackages, + default: changedPackages, + message: `The packages that this commit has affected (${changedPackages.length} detected)\n`, + name: 'packages', + type: 'checkbox' + }; + + return question; +}; diff --git a/lib/questions/scope.js b/lib/questions/scope.js new file mode 100644 index 00000000..b62b9732 --- /dev/null +++ b/lib/questions/scope.js @@ -0,0 +1,25 @@ +exports.createQuestion = (state) => { + const {scopes} = state.config; + + if (!scopes) { + return null; + } + + if (!Array.isArray(scopes)) { + throw new TypeError('scopes must be an array of strings.'); + } + + if (scopes.length < 1) { + return null; + } + + const question = { + choices: scopes, + default: 0, + message: 'Select the scope this component affects:\n', + name: 'scope', + type: 'list' + }; + + return question; +}; diff --git a/lib/questions/title.js b/lib/questions/title.js new file mode 100644 index 00000000..a3d66929 --- /dev/null +++ b/lib/questions/title.js @@ -0,0 +1,26 @@ +exports.createQuestion = (state) => { + const {config} = state; + const minTitleLengthErrorMessage = `The subject must have at least ${config.minMessageLength} characters`; + const question = { + filter: (input) => { + let subject; + + subject = input.trim(); + while (subject.endsWith('.')) { + subject = subject.substr(0, subject.length - 1).trim(); + } + + return subject; + }, + leadingLabel: (answers) => `${answers.type}:`, + + // Minus 3 chars are for emoji + space. + maxLength: config.maxMessageLength - 3, + message: 'Write a short, imperative mood description of the change:', + name: 'subject', + type: 'limitedInput', + validate: (input) => input.length >= config.minMessageLength || minTitleLengthErrorMessage + }; + + return question; +}; diff --git a/lib/steps/type.js b/lib/questions/type.js similarity index 86% rename from lib/steps/type.js rename to lib/questions/type.js index bf064534..571eb0b8 100644 --- a/lib/steps/type.js +++ b/lib/questions/type.js @@ -5,7 +5,8 @@ const typeToListItem = ({description, emoji, value}) => ({ value }); -exports.createQuestion = (state, config) => { +exports.createQuestion = (state) => { + const {config} = state; const question = { choices: config.list.map((type) => typeToListItem(config.types[type])), message: 'Select the type of change that you\'re committing:\n', diff --git a/lib/runInteractiveQuestions.js b/lib/runInteractiveQuestions.js new file mode 100644 index 00000000..3220f8b6 --- /dev/null +++ b/lib/runInteractiveQuestions.js @@ -0,0 +1,21 @@ +const inquirer = require('inquirer'); +const LimitedInputPrompt = require('./LimitedInputPrompt'); +const createQuestions = require('./createQuestions'); + +inquirer.registerPrompt('limitedInput', LimitedInputPrompt); + +// if (IS_LERNA_PROJECT) { +// const allPackages = getAllPackages().map((pkg) => pkg.name); +// const changedPackages = getChangedPackages(); +// +// promptQuestions = promptQuestions.concat(createPackagesQuestion(allPackages, changedPackages)); +// } + +const runInteractiveQuestions = async (state) => { + const questions = createQuestions(state); + const answers = await inquirer.prompt(questions); + + return answers; +}; + +module.exports = runInteractiveQuestions; diff --git a/lib/steps/breakingChanges.js b/lib/steps/breakingChanges.js deleted file mode 100644 index e69de29b..00000000 diff --git a/lib/steps/lerna.js b/lib/steps/lerna.js deleted file mode 100644 index e69de29b..00000000 diff --git a/lib/steps/scope.js b/lib/steps/scope.js deleted file mode 100644 index e69de29b..00000000 diff --git a/lib/steps/title.js b/lib/steps/title.js deleted file mode 100644 index e69de29b..00000000 diff --git a/lib/util/isLerna.js b/lib/util/isLerna.js new file mode 100644 index 00000000..e713c46e --- /dev/null +++ b/lib/util/isLerna.js @@ -0,0 +1,7 @@ +const path = require('path'); +const fs = require('fs'); + +const isLerna = (state) => + fs.existsSync(path.join(state.root, 'lerna.json')); + +module.exports = isLerna;