Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

WIP: feat: add commitlint config command #169

Closed
wants to merge 13 commits into from
4 changes: 2 additions & 2 deletions @alias/commitlint-config-lerna-scopes/package.json
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
{
"name": "commitlint-config-lerna-scopes",
"version": "5.2.6",
"version": "5.3.0-1",
"description": "Shareable commitlint config enforcing lerna package names as scopes",
"scripts": {
"test": "exit 0",
Expand All @@ -24,6 +24,6 @@
},
"homepage": "https://github.com/marionebl/commitlint#readme",
"dependencies": {
"@commitlint/config-lerna-scopes": "^5.2.6"
"@commitlint/config-lerna-scopes": "^5.3.0-1"
}
}
4 changes: 2 additions & 2 deletions @alias/commitlint/package.json
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
{
"name": "commitlint",
"version": "5.2.6",
"version": "5.3.0-1",
"description": "Lint your commit messages",
"bin": {
"commitlint": "cli.js"
Expand Down Expand Up @@ -28,7 +28,7 @@
},
"license": "MIT",
"dependencies": {
"@commitlint/cli": "^5.2.6",
"@commitlint/cli": "^5.3.0-1",
"read-pkg": "3.0.0",
"resolve-pkg": "1.0.0"
}
Expand Down
7 changes: 4 additions & 3 deletions @commitlint/cli/package.json
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
{
"name": "@commitlint/cli",
"version": "5.2.6",
"version": "5.3.0-1",
"description": "Lint your commit messages",
"bin": {
"commitlint": "./lib/cli.js"
Expand Down Expand Up @@ -50,7 +50,7 @@
},
"license": "MIT",
"devDependencies": {
"@commitlint/test": "^5.2.6",
"@commitlint/test": "^5.3.0-1",
"@commitlint/utils": "^5.1.1",
"ava": "0.18.2",
"babel-cli": "6.26.0",
Expand All @@ -69,8 +69,9 @@
"xo": "0.18.2"
},
"dependencies": {
"@commitlint/core": "^5.2.6",
"@commitlint/core": "^5.3.0-1",
"babel-polyfill": "6.26.0",
"babel-runtime": "^6.26.0",
"chalk": "2.3.0",
"get-stdin": "5.0.1",
"lodash": "4.17.4",
Expand Down
262 changes: 120 additions & 142 deletions @commitlint/cli/src/cli.js
Original file line number Diff line number Diff line change
@@ -1,193 +1,171 @@
#!/usr/bin/env node
require('babel-polyfill'); // eslint-disable-line import/no-unassigned-import

const core = require('@commitlint/core');
const chalk = require('chalk');
const meow = require('meow');
const {merge, pick} = require('lodash');
const stdin = require('get-stdin');
const merge = require('lodash/merge');

const pkg = require('../package');
const help = require('./help');

const configuration = {
string: ['cwd', 'from', 'to', 'edit', 'extends', 'parser-preset'],
boolean: ['help', 'version', 'quiet', 'color'],
alias: {
c: 'color',
d: 'cwd',
e: 'edit',
f: 'from',
t: 'to',
q: 'quiet',
h: 'help',
v: 'version',
x: 'extends',
p: 'parser-preset'
},
description: {
color: 'toggle colored output',
cwd: 'directory to execute in',
edit:
'read last commit message from the specified file or fallbacks to ./.git/COMMIT_EDITMSG',
extends: 'array of shareable configurations to extend',
from: 'lower end of the commit range to lint; applies if edit=false',
to: 'upper end of the commit range to lint; applies if edit=false',
quiet: 'toggle console output',
'parser-preset':
'configuration preset to use for conventional-commits-parser'
},
default: {
color: true,
cwd: process.cwd(),
edit: false,
from: null,
to: null,
quiet: false
},
unknown(arg) {
throw new Error(`unknown flags: ${arg}`);
}
};
const commands = require('./commands');

const FORMATS = ['commitlint', 'json'];
const COMMANDS = ['config'];

const HELP = `
Commands
commitlint lint commits, [input] reads from stdin if --edit, --from and --to are omitted

Options
--cwd, -d directory to execute in, defaults to: process.cwd()
--extends, -x array of shareable configurations to extend
--format, -o format to use, defaults to "commitlint". available: "commitlint", "json"
--parser-preset, -p configuration preset to use for conventional-commits-parser
--quiet, -q toggle console output

commitlint
--color, -c toggle colored output, defaults to: true
--edit, -e read last commit message from the specified file or falls back to ./.git/COMMIT_EDITMSG
--from, -f lower end of the commit range to lint; applies if edit=false
--to, -t upper end of the commit range to lint; applies if edit=false

Usage
$ echo "some commit" | commitlint
$ commitlint --to=master
$ commitlint --from=HEAD~1
`;

const cli = meow(
{
help: `[input] reads from stdin if --edit, --from and --to are omitted\n${help(
configuration
)}`,
help: HELP,
description: `${pkg.name}@${pkg.version} - ${pkg.description}`
},
configuration
{
string: ['cwd', 'format', 'from', 'to', 'edit', 'extends', 'parser-preset'],
boolean: ['help', 'version', 'quiet', 'color'],
alias: {
c: 'color',
d: 'cwd',
e: 'edit',
f: 'from',
h: 'help',
o: 'format',
p: 'parser-preset',
q: 'quiet',
t: 'to',
v: 'version',
x: 'extends'
},
default: {
color: true,
cwd: process.cwd(),
edit: false,
from: null,
to: null,
quiet: false
},
unknown(arg) {
if (COMMANDS.includes(arg)) {
return;
}

console.log(HELP);

if (!arg.startsWith('-') && !COMMANDS.includes(arg)) {
console.log(`<command> must be on of: [config], received "${arg}"`);
} else {
console.log(`unknown flags: ${arg}`);
}

process.exit(1);
}
}
);

main(cli).catch(err =>
setTimeout(() => {
if (err.quiet) {
process.exit(1);
}
if (err.help) {
console.log(`${cli.help}\n`);
}
if (err.type === pkg.name) {
console.log(err.message);
process.exit(1);
}
throw err;
})
);

async function main(options) {
const raw = options.input;
const flags = normalizeFlags(options.flags);
const fromStdin = checkFromStdin(raw, flags);
const raw = Array.isArray(options.input) ? options.input : [];
const [command] = raw;

const range = pick(flags, 'edit', 'from', 'to');
const fmt = new chalk.constructor({enabled: flags.color});

const input = await (fromStdin
? stdin()
: core.read(range, {cwd: flags.cwd}));

const messages = (Array.isArray(input) ? input : [input])
.filter(message => typeof message === 'string')
.filter(Boolean);
const flags = normalizeFlags(options.flags);

if (messages.length === 0 && !checkFromRepository(flags)) {
const err = new Error(
'[input] is required: supply via stdin, or --edit or --from and --to'
);
err.type = pkg.name;
console.log(`${cli.help}\n`);
console.log(err.message);
throw err;
if (!command) {
return commands.lint(raw, flags);
}

return Promise.all(
messages.map(async message => {
const loaded = await core.load(getSeed(flags), {cwd: flags.cwd});
const parserOpts = selectParserOpts(loaded.parserPreset);
const opts = parserOpts ? {parserOpts} : {parserOpts: {}};

// Strip comments if reading from `.git/COMMIT_EDIT_MSG`
if (range.edit) {
opts.parserOpts.commentChar = '#';
}

const report = await core.lint(message, loaded.rules, opts);
const formatted = core.format(report, {color: flags.color});

if (!flags.quiet) {
console.log(
`${fmt.grey('⧗')} input: ${fmt.bold(message.split('\n')[0])}`
);
console.log(formatted.join('\n'));
}

if (report.errors.length > 0) {
const error = new Error(formatted[formatted.length - 1]);
error.type = pkg.name;
throw error;
}
console.log('');
})
);
}

function checkFromStdin(input, flags) {
return input.length === 0 && !checkFromRepository(flags);
}

function checkFromRepository(flags) {
return checkFromHistory(flags) || checkFromEdit(flags);
}

function checkFromEdit(flags) {
return Boolean(flags.edit);
switch (command) {
case 'config':
return commands.config(raw, {
cwd: flags.cwd,
extends: flags.extends,
format: flags.format,
parserPreset: flags.parserPreset
});
default: {
const err = new Error(
`<command> must be on of: [config], received "${command}"`
);
err.help = true;
err.type = pkg.name;
throw err;
}
}
}

function checkFromHistory(flags) {
return typeof flags.from === 'string' || typeof flags.to === 'string';
}
function normalizeFlags(raw) {
const flags = merge({}, raw);

function normalizeFlags(flags) {
// The `edit` flag is either a boolean or a string but we are only allowed
// to specify one of them in minimist
const edit = flags.edit === '' ? true : normalizeEdit(flags.edit);
return merge({}, flags, {edit, e: edit});
}

function normalizeEdit(edit) {
if (typeof edit === 'boolean') {
return edit;
if (flags.edit === '') {
merge(flags, {edit: true, e: true});
}

// The recommended method to specify -e with husky is commitlint -e $GIT_PARAMS
// This does not work properly with win32 systems, where env variable declarations
// use a different syntax
// See https://github.com/marionebl/commitlint/issues/103 for details
if (edit === '$GIT_PARAMS' || edit === '%GIT_PARAMS%') {
if (flags.edit === '$GIT_PARAMS' || flags.edit === '%GIT_PARAMS%') {
if (!('GIT_PARAMS' in process.env)) {
throw new Error(
`Received ${edit} as value for -e | --edit, but GIT_PARAMS is not available globally.`
`Received ${
flags.edit
} as value for -e | --edit, but GIT_PARAMS is not available globally.`
);
}
return process.env.GIT_PARAMS;
}
return edit;
}

function getSeed(seed) {
const e = Array.isArray(seed.extends) ? seed.extends : [seed.extends];
const n = e.filter(i => typeof i === 'string');
return n.length > 0
? {extends: n, parserPreset: seed.parserPreset}
: {parserPreset: seed.parserPreset};
}

function selectParserOpts(parserPreset) {
if (typeof parserPreset !== 'object') {
return undefined;
if (!('format' in flags)) {
flags.format = 'commitlint';
}

const opts = parserPreset.opts;

if (typeof opts !== 'object') {
return undefined;
if (!FORMATS.includes(flags.format)) {
const err = new Error(
`--format must be on of: [${FORMATS.join(',')}], received "${
flags.format
}".`
);
err.quiet = flags.quiet;
err.help = true;
err.type = pkg.name;
throw err;
}

return opts.parserOpts;
return flags;
}

// Catch unhandled rejections globally
Expand Down
Loading