Skip to content

Commit fd77856

Browse files
committed
feat: add --format flag
1 parent 97b353b commit fd77856

File tree

3 files changed

+137
-33
lines changed

3 files changed

+137
-33
lines changed

@commitlint/cli/src/cli.js

+26-6
Original file line numberDiff line numberDiff line change
@@ -7,6 +7,8 @@ const merge = require('lodash/merge');
77
const pkg = require('../package');
88
const commands = require('./commands');
99

10+
const FORMATS = ['commitlint', 'json'];
11+
1012
const cli = meow(
1113
{
1214
help: `
@@ -16,12 +18,13 @@ const cli = meow(
1618
Options
1719
--cwd, -d directory to execute in, defaults to: process.cwd()
1820
--extends, -x array of shareable configurations to extend
21+
--format, -o formatter to use, defaults to "commitlint". available: "commitlint", "json"
1922
--parser-preset, -p configuration preset to use for conventional-commits-parser
2023
--quiet, -q toggle console output
2124
2225
commitlint
2326
--color, -c toggle colored output, defaults to: true
24-
--edit, -e read last commit message from the specified file or fallbacks to ./.git/COMMIT_EDITMSG
27+
--edit, -e read last commit message from the specified file or falls back to ./.git/COMMIT_EDITMSG
2528
--from, -f lower end of the commit range to lint; applies if edit=false
2629
--to, -t upper end of the commit range to lint; applies if edit=false
2730
@@ -33,19 +36,20 @@ const cli = meow(
3336
description: `${pkg.name}@${pkg.version} - ${pkg.description}`
3437
},
3538
{
36-
string: ['cwd', 'from', 'to', 'edit', 'extends', 'parser-preset'],
39+
string: ['cwd', 'format', 'from', 'to', 'edit', 'extends', 'parser-preset'],
3740
boolean: ['help', 'version', 'quiet', 'color'],
3841
alias: {
3942
c: 'color',
4043
d: 'cwd',
4144
e: 'edit',
4245
f: 'from',
43-
t: 'to',
44-
q: 'quiet',
4546
h: 'help',
47+
o: 'format',
48+
p: 'parser-preset',
49+
q: 'quiet',
50+
t: 'to',
4651
v: 'version',
47-
x: 'extends',
48-
p: 'parser-preset'
52+
x: 'extends'
4953
},
5054
default: {
5155
color: true,
@@ -94,6 +98,22 @@ function normalizeFlags(flags) {
9498
return merge({}, flags, {edit: true, e: true});
9599
}
96100

101+
if (!('format' in flags)) {
102+
flags.format = 'commitlint';
103+
}
104+
105+
if (!FORMATS.includes(flags.format)) {
106+
const err = new Error(
107+
`--format must be on of: [${FORMATS.join(',')}], received "${
108+
flags.format
109+
}".`
110+
);
111+
err.quiet = flags.quiet;
112+
err.help = true;
113+
err.type = pkg.name;
114+
throw err;
115+
}
116+
97117
return flags;
98118
}
99119

@commitlint/cli/src/cli.test.js

+50
Original file line numberDiff line numberDiff line change
@@ -82,6 +82,56 @@ test('should produce no error output with -q flag', async t => {
8282
t.is(actual.code, 1);
8383
});
8484

85+
test('should produce json output with --format=json', async t => {
86+
const cwd = await git.bootstrap('fixtures/empty');
87+
const actual = await cli(['--format=json'], {cwd})('foo: bar');
88+
t.notThrows(() => JSON.parse(actual.stdout));
89+
});
90+
91+
test('should produce no output when --quiet and --format=json', async t => {
92+
const cwd = await git.bootstrap('fixtures/empty');
93+
const actual = await cli(['--format=json', '--quiet'], {cwd})('foo: bar');
94+
t.is(actual.stdout, '');
95+
t.is(actual.stderr, '');
96+
});
97+
98+
test('should produce json with expected truthy valid key', async t => {
99+
const cwd = await git.bootstrap('fixtures/empty');
100+
const actual = await cli(['--format=json'], {cwd})('foo: bar');
101+
const data = JSON.parse(actual.stdout);
102+
t.is(data.valid, true);
103+
});
104+
105+
test('should produce json with expected falsy valid key', async t => {
106+
const cwd = await git.bootstrap('fixtures/simple');
107+
const actual = await cli(['--format=json'], {cwd})('foo: bar');
108+
const data = JSON.parse(actual.stdout);
109+
t.is(data.valid, false);
110+
});
111+
112+
test('should produce json with results array', async t => {
113+
const cwd = await git.bootstrap('fixtures/empty');
114+
const actual = await cli(['--format=json'], {cwd})('foo: bar');
115+
const data = JSON.parse(actual.stdout);
116+
t.is(Array.isArray(data.results), true);
117+
});
118+
119+
test('results array has one report for one message', async t => {
120+
const cwd = await git.bootstrap('fixtures/empty');
121+
const actual = await cli(['--format=json'], {cwd})('foo: bar');
122+
const data = JSON.parse(actual.stdout);
123+
t.is(data.results.length, 1);
124+
});
125+
126+
test('report has expected schema', async t => {
127+
const cwd = await git.bootstrap('fixtures/empty');
128+
const actual = await cli(['--format=json'], {cwd})('foo: bar');
129+
const data = JSON.parse(actual.stdout);
130+
const result = data.results[0];
131+
t.is('report' in result, true);
132+
t.is('input' in result, true);
133+
});
134+
85135
test('should work with husky commitmsg hook and git commit', async () => {
86136
const cwd = await git.bootstrap('fixtures/husky/integration');
87137
await writePkg({scripts: {commitmsg: `${bin} -e`}}, {cwd});

@commitlint/cli/src/commands/lint.js

+61-27
Original file line numberDiff line numberDiff line change
@@ -12,7 +12,6 @@ async function lint(rawInput, flags) {
1212
const fromStdin = checkFromStdin(rawInput, flags);
1313

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

1716
const input = await (fromStdin
1817
? stdin()
@@ -23,39 +22,74 @@ async function lint(rawInput, flags) {
2322
.filter(Boolean);
2423

2524
if (messages.length === 0 && !checkFromRepository(flags)) {
26-
const err = new Error(
27-
'[input] is required: supply via stdin, or --edit or --from and --to'
25+
throw error(
26+
'[input] is required: supply via stdin, or --edit or --from and --to',
27+
{
28+
quiet: flags.quiet,
29+
help: true,
30+
type: pkg.name
31+
}
2832
);
29-
err.quiet = flags.quiet;
30-
err.help = true;
31-
err.type = pkg.name;
32-
throw err;
3333
}
3434

35-
return Promise.all(
36-
messages.map(async message => {
37-
const loaded = await core.load(getSeed(flags), {cwd: flags.cwd});
38-
const parserOpts = selectParserOpts(loaded.parserPreset);
39-
const opts = parserOpts ? {parserOpts} : undefined;
40-
const report = await core.lint(message, loaded.rules, opts);
41-
const formatted = core.format(report, {color: flags.color});
35+
const loaded = await core.load(getSeed(flags), {cwd: flags.cwd});
36+
const parserOpts = selectParserOpts(loaded.parserPreset);
37+
const opts = parserOpts ? {parserOpts} : undefined;
38+
39+
const results = await all(messages, async msg => {
40+
return {
41+
report: await core.lint(msg, loaded.rules, opts),
42+
input: msg
43+
};
44+
});
45+
46+
const valid = results.every(result => result.report.valid);
47+
48+
if (flags.quiet && valid) {
49+
return;
50+
}
51+
52+
if (flags.quiet && !valid) {
53+
throw error('linting failed', {type: pkg.name, quiet: true});
54+
}
4255

43-
if (!flags.quiet) {
56+
switch (flags.format) {
57+
case 'commitlint': {
58+
const fmt = new chalk.constructor({enabled: flags.color});
59+
const icon = fmt.grey('⧗');
60+
const formatted = results.map(result => {
61+
result.formatted = core.format(result.report, {color: flags.color});
62+
return result;
63+
});
64+
formatted.forEach(result => {
65+
const subject = fmt.bold(result.input.split('\n')[0]);
4466
console.log(
45-
`${fmt.grey('⧗')} input: ${fmt.bold(message.split('\n')[0])}`
67+
`${icon} input: ${subject}\n${result.formatted.join('\n')}\n`
4668
);
47-
console.log(formatted.join('\n'));
48-
}
69+
});
70+
break;
71+
}
72+
case 'json':
73+
console.log(JSON.stringify({valid, results}));
74+
break;
75+
default: {
76+
throw error(`unknown format: ${flags.format}`);
77+
}
78+
}
4979

50-
if (report.errors.length > 0) {
51-
const error = new Error(formatted[formatted.length - 1]);
52-
error.quiet = flags.quiet;
53-
error.type = pkg.name;
54-
throw error;
55-
}
56-
console.log('');
57-
})
58-
);
80+
if (!valid) {
81+
throw error('linting failed', {type: pkg.name, quiet: true});
82+
}
83+
}
84+
85+
function all(things, predecate) {
86+
return Promise.all(things.map(thing => predecate(thing)));
87+
}
88+
89+
function error(message, opts = {}) {
90+
const err = new Error(message);
91+
Object.assign(err, opts);
92+
return err;
5993
}
6094

6195
function selectParserOpts(parserPreset) {

0 commit comments

Comments
 (0)