Skip to content

Commit f3308d6

Browse files
committedNov 10, 2017
logger -> cli
1 parent 7c1a97f commit f3308d6

9 files changed

+201
-139
lines changed
 

‎bin/get_metadata.js

+7-4
Original file line numberDiff line numberDiff line change
@@ -3,12 +3,15 @@
33

44
const argv = require('../lib/args')();
55
const getMetadata = require('../steps/metadata');
6-
const loggerFactory = require('../lib/logger');
6+
const CLI = require('../lib/cli');
77

88
const logStream = process.stdout.isTTY ? process.stdout : process.stderr;
9-
const logger = loggerFactory(logStream);
9+
const cli = new CLI(logStream);
1010

11-
getMetadata(argv, logger).catch((err) => {
12-
logger.error(err);
11+
getMetadata(argv, cli).catch((err) => {
12+
if (cli.spinner.enabled) {
13+
cli.spinner.fail();
14+
}
15+
cli.error(err);
1316
process.exit(-1);
1417
});

‎lib/cli.js

+117
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,117 @@
1+
'use strict';
2+
3+
const {
4+
tick, cross, info: infoRaw, warning: warningRaw
5+
} = require('figures');
6+
const ora = require('ora');
7+
const { EOL } = require('os');
8+
const chalk = require('chalk');
9+
10+
const warning = chalk.yellow(warningRaw);
11+
const error = chalk.red(cross);
12+
const info = chalk.blue(infoRaw);
13+
const success = chalk.green(tick);
14+
15+
const SPINNER_STATUS = {
16+
SUCCESS: 'success',
17+
FAILED: 'failed',
18+
WARN: 'warn',
19+
INFO: 'info'
20+
};
21+
22+
const { SUCCESS, FAILED, WARN, INFO } = SPINNER_STATUS;
23+
24+
function head(text, length = 8) {
25+
return chalk.bold(text.padEnd(length));
26+
}
27+
28+
class CLI {
29+
constructor(stream) {
30+
this.stream = stream || process.stderr;
31+
this.spinner = ora({ stream });
32+
}
33+
34+
startSpinner(text) {
35+
this.spinner.text = text;
36+
this.spinner.start();
37+
}
38+
39+
updateSpinner(text) {
40+
this.spinner.text = text;
41+
}
42+
43+
stopSpinner(rawText, status = SUCCESS) {
44+
let symbol;
45+
if (status === SUCCESS) {
46+
symbol = success;
47+
} else if (status === FAILED) {
48+
symbol = error;
49+
} else if (status === WARN) {
50+
symbol = warning;
51+
} else if (status === INFO) {
52+
symbol = info;
53+
}
54+
const text = ' ' + rawText;
55+
this.spinner.stopAndPersist({
56+
symbol, text
57+
});
58+
}
59+
60+
write(text) {
61+
this.stream.write(text);
62+
}
63+
64+
log(text) {
65+
this.write(text + EOL);
66+
}
67+
68+
table(first, second, length) {
69+
this.log(head(first, length) + second);
70+
}
71+
72+
separator(text = '', length = 80, sep = '-') {
73+
if (!text) {
74+
this.log(sep.repeat(length));
75+
return;
76+
}
77+
const rest = (length - text.length - 2);
78+
const half = sep.repeat(Math.floor(rest / 2));
79+
if (rest % 2 === 0) {
80+
this.log(`${half} ${chalk.bold(text)} ${half}`);
81+
} else {
82+
this.log(`${half} ${chalk.bold(text)} ${sep}${half}`);
83+
}
84+
}
85+
86+
ok(text, options = {}) {
87+
const prefix = options.newline ? EOL : '';
88+
this.log(`${prefix}${success} ${text}`);
89+
}
90+
91+
warn(text, options = {}) {
92+
const prefix = options.newline ? EOL : '';
93+
this.log(`${prefix}${warning} ${text}`);
94+
}
95+
96+
info(text, options = {}) {
97+
const prefix = options.newline ? EOL : '';
98+
this.log(`${prefix}${info} ${text}`);
99+
}
100+
101+
error(obj, options = {}) {
102+
const prefix = options.newline ? EOL : '';
103+
if (obj instanceof Error) {
104+
this.log(`${prefix}${error} ${obj.message}`);
105+
this.log(`${obj.stack}`);
106+
this.log(`${JSON.stringify(obj.data, null, 2).replace(/\n/g, EOL)}`);
107+
} else {
108+
this.log(`${prefix}${error} ${obj}`);
109+
}
110+
}
111+
};
112+
113+
CLI.SPINNER_STATUS = SPINNER_STATUS;
114+
CLI.SCISSOR_LEFT = '>8';
115+
CLI.SCISSOR_RIGHT = '8<';
116+
117+
module.exports = CLI;

‎lib/collaborators.js

+3-3
Original file line numberDiff line numberDiff line change
@@ -41,7 +41,7 @@ Collaborator.TYPES = {
4141
TSC, COLLABORATOR
4242
};
4343

44-
async function getCollaborators(readme, logger, owner, repo) {
44+
async function getCollaborators(readme, cli, owner, repo) {
4545
// This is more or less taken from
4646
// https://github.com/rvagg/iojs-tools/blob/master/pr-metadata/pr-metadata.js
4747
const members = new Map();
@@ -68,8 +68,8 @@ async function getCollaborators(readme, logger, owner, repo) {
6868
if (!(tscIndex < tsceIndex &&
6969
tsceIndex < clIndex &&
7070
clIndex < cleIndex)) {
71-
logger.warn('Order of different types of contacts needs update. ' +
72-
'Analysis could get wrong.');
71+
cli.warn('Contacts in the README is out of order, ' +
72+
'analysis could go wrong.', { newline: true });
7373
}
7474

7575
// We also assume that TSC & TSC Emeriti are also listed as collaborators

‎lib/logger.js

-62
This file was deleted.

‎lib/metadata_gen.js

+2-7
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,7 @@
11
'use strict';
22

33
const LinkParser = require('./links');
4-
const { EOL } = require('os');
4+
55
/**
66
* @typedef {{reviewer: Collaborator}} Reviewer
77
*/
@@ -40,13 +40,8 @@ class MetadataGenerator {
4040
'' // creates final EOL
4141
];
4242

43-
return meta.join(EOL);
43+
return meta.join('\n');
4444
}
4545
}
4646

47-
MetadataGenerator.SCISSORS = [
48-
`-------------------------------- >8 --------------------------------${EOL}`,
49-
`-------------------------------- 8< --------------------------------${EOL}`
50-
];
51-
5247
module.exports = MetadataGenerator;

‎lib/pr_checker.js

+29-30
Original file line numberDiff line numberDiff line change
@@ -26,11 +26,11 @@ const { FULL } = CIParser.constants;
2626

2727
class PRChecker {
2828
/**
29-
* @param {{}} logger
29+
* @param {{}} cli
3030
* @param {PRData} data
3131
*/
32-
constructor(logger, data) {
33-
this.logger = logger;
32+
constructor(cli, data) {
33+
this.cli = cli;
3434
const {
3535
pr, reviewers, comments, reviews, commits, collaborators
3636
} = data;
@@ -81,33 +81,33 @@ class PRChecker {
8181

8282
checkReviews(comments = false) {
8383
const {
84-
pr, logger, reviewers: { rejected, approved }
84+
pr, cli, reviewers: { rejected, approved }
8585
} = this;
8686
let status = true;
8787

8888
if (rejected.length === 0) {
89-
logger.info(`Rejections: 0`);
89+
cli.ok(`Rejections: 0`);
9090
} else {
9191
status = false;
9292
let hint = this.getTSCHint(rejected);
93-
logger.warn(`Rejections: ${rejected.length}${hint}`);
93+
cli.error(`Rejections: ${rejected.length}${hint}`);
9494
for (const { reviewer, review } of rejected) {
95-
logger.warn(`${reviewer.getName()} rejected in ${review.ref}`);
95+
cli.error(`- ${reviewer.getName()}: ${review.ref}`);
9696
}
9797
}
9898
if (approved.length === 0) {
9999
status = false;
100-
logger.warn(`Approvals: 0`);
100+
cli.error(`Approvals: 0`);
101101
} else {
102102
let hint = this.getTSCHint(approved);
103-
logger.info(`Approvals: ${approved.length}${hint}`);
103+
cli.ok(`Approvals: ${approved.length}${hint}`);
104104

105105
if (comments) {
106106
for (const {reviewer, review} of approved) {
107107
if (review.source === FROM_COMMENT ||
108108
review.source === FROM_REVIEW_COMMENT) {
109-
logger.warn(
110-
`${reviewer.getName()} approved in via LGTM in comments`);
109+
cli.info(
110+
`- ${reviewer.getName()} approved in via LGTM in comments`);
111111
}
112112
}
113113
}
@@ -117,7 +117,7 @@ class PRChecker {
117117
const tscApproval = approved.filter((p) => p.reviewer.isTSC()).length;
118118
if (tscApproval < 2) {
119119
status = false;
120-
logger.warn('semver-major requires at least two TSC approvals');
120+
cli.error('semver-major requires at least two TSC approvals');
121121
}
122122
}
123123
}
@@ -150,7 +150,7 @@ class PRChecker {
150150
*/
151151
checkPRWait(now) {
152152
const { pr } = this;
153-
const { logger } = this;
153+
const { cli } = this;
154154
const labels = pr.labels.nodes;
155155
const fast = labels.some((l) => l.name === 'code-and-learn') ||
156156
(labels.length === 1 && labels[0].name === 'doc');
@@ -159,8 +159,8 @@ class PRChecker {
159159
if (wait.timeLeft > 0) {
160160
const dateStr = new Date(pr.createdAt).toDateString();
161161
const type = wait.isWeekend ? 'weekend' : 'weekday';
162-
logger.info(`This PR was created on ${dateStr} (${type} in UTC)`);
163-
logger.warn(`${wait.timeLeft} hours left to land`);
162+
cli.info(`This PR was created on ${dateStr} (${type} in UTC)`);
163+
cli.warn(`${wait.timeLeft} hours left to land`);
164164
return false;
165165
}
166166

@@ -170,7 +170,7 @@ class PRChecker {
170170
// TODO: we might want to check CI status when it's less flaky...
171171
// TODO: not all PR requires CI...labels?
172172
checkCI() {
173-
const { pr, logger, comments, reviews, commits } = this;
173+
const { pr, cli, comments, reviews, commits } = this;
174174
const prNode = {
175175
publishedAt: pr.createdAt,
176176
bodyText: pr.bodyText
@@ -179,17 +179,17 @@ class PRChecker {
179179
const ciMap = new CIParser(thread).parse();
180180
let status = true;
181181
if (!ciMap.size) {
182-
logger.warn('No CI runs detected');
182+
cli.error('No CI runs detected');
183183
return false;
184184
} else if (!ciMap.get(FULL)) {
185185
status = false;
186-
logger.warn('No full CI runs detected');
186+
cli.error('No full CI runs detected');
187187
}
188188

189189
let lastCI;
190190
for (const [type, ci] of ciMap) {
191191
const name = CI_TYPES.get(type).name;
192-
logger.info(`Last ${name} CI on ${ci.date}: ${ci.link}`);
192+
cli.info(`Last ${name} CI on ${ci.date}: ${ci.link}`);
193193
if (!lastCI || lastCI.date > ci.date) {
194194
lastCI = {
195195
typeName: name,
@@ -206,10 +206,10 @@ class PRChecker {
206206
if (!didLogHeader) {
207207
status = false;
208208
didLogHeader = true;
209-
logger.warn(`Commits pushed after the last ${lastCI.typeName} ` +
209+
cli.warn(`Commits pushed after the last ${lastCI.typeName} ` +
210210
'CI run:');
211211
}
212-
logger.warn(`- ${commit.message.split('\n')[0]}`);
212+
cli.warn(`- ${commit.message.split('\n')[0]}`);
213213
}
214214
});
215215
}
@@ -223,20 +223,19 @@ class PRChecker {
223223
}
224224

225225
checkAuthor() {
226-
const { logger, commits, pr } = this;
226+
const { cli, commits, pr } = this;
227227

228228
const oddCommits = this.filterOddCommits(commits);
229229
if (!oddCommits.length) {
230230
return true;
231231
}
232232

233233
const prAuthor = pr.author.login;
234-
logger.warn(`PR is opened by @${prAuthor}`);
234+
cli.warn(`PR author is: @${prAuthor}`);
235235
for (const c of oddCommits) {
236236
const { oid, author } = c.commit;
237237
const hash = oid.slice(0, 7);
238-
logger.warn(`Author ${author.email} of commit ${hash} ` +
239-
`does not match committer or PR author`);
238+
cli.warn(`- commit ${hash} is authored by ${author.email}`);
240239
}
241240
return false;
242241
}
@@ -273,7 +272,7 @@ class PRChecker {
273272

274273
checkCommitsAfterReview() {
275274
const {
276-
commits, reviews, logger
275+
commits, reviews, cli
277276
} = this;
278277

279278
if (reviews.length < 1) {
@@ -290,12 +289,12 @@ class PRChecker {
290289

291290
let status = true;
292291
if (commitDate > reviewDate) {
293-
logger.warn('Changes were pushed since the last review:');
292+
cli.warn('Changes pushed since the last review:');
294293
commits.forEach((commit) => {
295294
commit = commit.commit;
296295
if (commit.committedDate > reviewDate) {
297296
status = false;
298-
logger.warn(`- ${commit.message.split('\n')[0]}`);
297+
cli.warn(`- ${commit.message.split('\n')[0]}`);
299298
}
300299
});
301300
}
@@ -305,11 +304,11 @@ class PRChecker {
305304

306305
checkMergeableState() {
307306
const {
308-
pr, logger
307+
pr, cli
309308
} = this;
310309

311310
if (pr.mergeable && pr.mergeable === CONFLICTING) {
312-
logger.warn('This PR has conflicts that must be resolved');
311+
cli.warn('This PR has conflicts that must be resolved');
313312
return false;
314313
}
315314

‎lib/pr_data.js

+31-24
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,7 @@
11
'use strict';
22

3-
const { getCollaborators } = require('../lib/collaborators');
4-
const { ReviewAnalyzer } = require('../lib/reviews');
3+
const { getCollaborators } = require('./collaborators');
4+
const { ReviewAnalyzer } = require('./reviews');
55

66
// queries/*.gql file names
77
const PR_QUERY = 'PR';
@@ -15,15 +15,16 @@ class PRData {
1515
* @param {number} prid
1616
* @param {string} owner
1717
* @param {string} repo
18-
* @param {Object} logger
18+
* @param {Object} cli
1919
* @param {Object} request
2020
*/
21-
constructor(prid, owner, repo, logger, request) {
21+
constructor(prid, owner, repo, cli, request) {
2222
this.prid = prid;
2323
this.owner = owner;
2424
this.repo = repo;
25-
this.logger = logger;
25+
this.cli = cli;
2626
this.request = request;
27+
this.prStr = `${owner}/${repo}/pull/${prid}`;
2728

2829
// Data
2930
this.collaborators = new Map();
@@ -35,13 +36,17 @@ class PRData {
3536
}
3637

3738
async getAll() {
39+
const { prStr } = this;
40+
this.cli.startSpinner(`Loading data for ${prStr}`);
3841
await Promise.all([
3942
this.getCollaborators(),
4043
this.getPR(),
4144
this.getReviews(),
4245
this.getComments(),
4346
this.getCommits()
44-
]);
47+
]).then(() => {
48+
this.cli.stopSpinner(`Done loading data for ${prStr}`);
49+
});
4550
this.analyzeReviewers();
4651
}
4752

@@ -50,48 +55,48 @@ class PRData {
5055
}
5156

5257
async getCollaborators() {
53-
const { owner, repo, logger, request } = this;
54-
logger.trace(
58+
const { owner, repo, cli, request } = this;
59+
cli.updateSpinner(
5560
`Getting collaborator contacts from README of ${owner}/${repo}`);
5661
const url = `https://raw.githubusercontent.com/${owner}/${repo}/master/README.md`;
5762
const readme = await request.promise({ url });
58-
this.collaborators = await getCollaborators(readme, logger, owner, repo);
63+
this.collaborators = await getCollaborators(readme, cli, owner, repo);
5964
}
6065

6166
async getPR() {
62-
const { prid, owner, repo, logger, request } = this;
63-
logger.trace(`Getting PR from ${owner}/${repo}/pull/${prid}`);
67+
const { prid, owner, repo, cli, request, prStr } = this;
68+
cli.updateSpinner(`Getting PR from ${prStr}`);
6469
const prData = await request.gql(PR_QUERY, { prid, owner, repo });
6570
const pr = this.pr = prData.repository.pullRequest;
6671
// Get the mail
67-
logger.trace(`Getting User information for ${pr.author.login}`);
72+
cli.updateSpinner(`Getting User information for ${pr.author.login}`);
6873
const userData = await request.gql(USER_QUERY, { login: pr.author.login });
6974
const user = userData.user;
7075
Object.assign(this.pr.author, user);
7176
}
7277

7378
async getReviews() {
74-
const { prid, owner, repo, logger, request } = this;
79+
const { prid, owner, repo, cli, request, prStr } = this;
7580
const vars = { prid, owner, repo };
76-
logger.trace(`Getting reviews from ${owner}/${repo}/pull/${prid}`);
81+
cli.updateSpinner(`Getting reviews from ${prStr}`);
7782
this.reviews = await request.gql(REVIEWS_QUERY, vars, [
7883
'repository', 'pullRequest', 'reviews'
7984
]);
8085
}
8186

8287
async getComments() {
83-
const { prid, owner, repo, logger, request } = this;
88+
const { prid, owner, repo, cli, request, prStr } = this;
8489
const vars = { prid, owner, repo };
85-
logger.trace(`Getting comments from ${owner}/${repo}/pull/${prid}`);
90+
cli.updateSpinner(`Getting comments from ${prStr}`);
8691
this.comments = await request.gql(COMMENTS_QUERY, vars, [
8792
'repository', 'pullRequest', 'comments'
8893
]);
8994
}
9095

9196
async getCommits() {
92-
const { prid, owner, repo, logger, request } = this;
97+
const { prid, owner, repo, cli, request, prStr } = this;
9398
const vars = { prid, owner, repo };
94-
logger.trace(`Getting commits from ${owner}/${repo}/pull/${prid}`);
99+
cli.updateSpinner(`Getting commits from ${prStr}`);
95100
this.commits = await request.gql(COMMITS_QUERY, vars, [
96101
'repository', 'pullRequest', 'commits'
97102
]);
@@ -100,7 +105,7 @@ class PRData {
100105
logIntro() {
101106
const {
102107
commits,
103-
logger,
108+
cli,
104109
owner,
105110
prid,
106111
pr: {
@@ -112,11 +117,13 @@ class PRData {
112117
}
113118
} = this;
114119

115-
logger.info(`${title} #${prid}`);
116-
logger.info(`${author} wants to merge ${commits.length} ` +
117-
`commit${commits.length === 1 ? '' : 's'} into ` +
118-
`${owner}:${baseRefName} from ${author}:${headRefName}`);
119-
logger.info(`Labels: ${labels.nodes.map(label => label.name).join(' ')}`);
120+
const branch = `${author}:${headRefName} -> ${owner}:${baseRefName}`;
121+
const labelStr = labels.nodes.map(label => label.name).join(', ');
122+
cli.table('Title', `${title} #${prid}`);
123+
cli.table('Author', `${author}`);
124+
cli.table('Commits', `${commits.length}`);
125+
cli.table('Branch', `${branch}`);
126+
cli.table('Labels', `${labelStr}`);
120127
}
121128
};
122129

‎package.json

+2-1
Original file line numberDiff line numberDiff line change
@@ -30,9 +30,10 @@
3030
"license": "MIT",
3131
"dependencies": {
3232
"chalk": "^2.2.0",
33+
"figures": "^2.0.0",
3334
"ghauth": "^3.2.1",
3435
"jsdom": "^11.3.0",
35-
"pino": "^4.9.0",
36+
"ora": "^1.3.0",
3637
"request": "^2.83.0",
3738
"request-promise-native": "^1.0.5",
3839
"yargs": "^10.0.3"

‎steps/metadata.js

+10-8
Original file line numberDiff line numberDiff line change
@@ -8,14 +8,15 @@ const MetadataGenerator = require('../lib/metadata_gen');
88

99
const fs = require('fs');
1010

11-
module.exports = async function getMetadata(argv, logger) {
11+
module.exports = async function getMetadata(argv, cli) {
1212
const { prid, owner, repo } = argv;
1313
const credentials = await auth();
1414
const request = new Request(credentials);
1515

16-
const data = new PRData(prid, owner, repo, logger, request);
16+
const data = new PRData(prid, owner, repo, cli, request);
1717
await data.getAll();
1818

19+
cli.separator('PR info');
1920
data.logIntro();
2021

2122
const metadata = new MetadataGenerator(data).getMetadata();
@@ -24,16 +25,17 @@ module.exports = async function getMetadata(argv, logger) {
2425
}
2526

2627
if (argv.file) {
27-
logger.info(`Writing metadata to ${argv.file}`);
28+
cli.separator();
29+
cli.startSpinner(`Writing metadata to ${argv.file}..`);
2830
fs.writeFileSync(argv.file, metadata);
31+
cli.stopSpinner(`Done writing metadata to ${argv.file}`);
2932
}
3033

31-
const [SCISSOR_LEFT, SCISSOR_RIGHT] = MetadataGenerator.SCISSORS;
32-
logger.info({
33-
raw: `${SCISSOR_LEFT}${metadata}${SCISSOR_RIGHT}`
34-
}, 'Generated metadata:');
34+
cli.separator('Generated metadata');
35+
cli.write(metadata);
36+
cli.separator();
3537

36-
const checker = new PRChecker(logger, data);
38+
const checker = new PRChecker(cli, data);
3739
const status = checker.checkAll(argv.checkComments);
3840
return {
3941
status,

0 commit comments

Comments
 (0)
Please sign in to comment.