@buildo/hophop
Version:
A minimal tool to accelerate the GitHub workflow from the command line.
652 lines (514 loc) • 20.2 kB
JavaScript
;
var _regeneratorRuntime = require('babel-runtime/regenerator')['default'];
var _Object$values = require('babel-runtime/core-js/object/values')['default'];
var _interopRequireDefault = require('babel-runtime/helpers/interop-require-default')['default'];
exports.__esModule = true;
var _lodash = require('lodash');
var _path = require('path');
var _path2 = _interopRequireDefault(_path);
var _ramda = require('ramda');
var _ramda2 = _interopRequireDefault(_ramda);
var _fs = require('fs');
var _fs2 = _interopRequireDefault(_fs);
var _es6Promisify = require('es6-promisify');
var _es6Promisify2 = _interopRequireDefault(_es6Promisify);
var _inquirer = require('inquirer');
var _inquirer2 = _interopRequireDefault(_inquirer);
var _utils = require('../utils');
var _toggl = require('../toggl');
function gh_create_issue() {
var _ref, repo, title, createIssue, created;
return _regeneratorRuntime.async(function gh_create_issue$(context$1$0) {
while (1) switch (context$1$0.prev = context$1$0.next) {
case 0:
context$1$0.next = 2;
return _regeneratorRuntime.awrap(_utils.gh_helper());
case 2:
_ref = context$1$0.sent;
repo = _ref.repo;
context$1$0.next = 6;
return _regeneratorRuntime.awrap(_utils.rl.question({ message: 'Title:' }));
case 6:
title = context$1$0.sent;
createIssue = _es6Promisify2['default'](repo.issue.bind(repo));
context$1$0.next = 10;
return _regeneratorRuntime.awrap(createIssue({ title: title }));
case 10:
created = context$1$0.sent;
_utils.log(created.html_url);
context$1$0.next = 14;
return _regeneratorRuntime.awrap(_utils.exec('open "' + created.html_url + '"'));
case 14:
return context$1$0.abrupt('return', created);
case 15:
case 'end':
return context$1$0.stop();
}
}, null, this);
}
function gh_fetch_issue(issueNumber) {
var _ref2, repo_name, client, issueRef, issue;
return _regeneratorRuntime.async(function gh_fetch_issue$(context$1$0) {
while (1) switch (context$1$0.prev = context$1$0.next) {
case 0:
context$1$0.next = 2;
return _regeneratorRuntime.awrap(_utils.gh_helper());
case 2:
_ref2 = context$1$0.sent;
repo_name = _ref2.repo_name;
client = _ref2.client;
context$1$0.next = 7;
return _regeneratorRuntime.awrap(client.issue(repo_name, issueNumber));
case 7:
issueRef = context$1$0.sent;
context$1$0.prev = 8;
context$1$0.next = 11;
return _regeneratorRuntime.awrap(_es6Promisify2['default'](issueRef.info.bind(issueRef))());
case 11:
issue = context$1$0.sent;
return context$1$0.abrupt('return', issue);
case 15:
context$1$0.prev = 15;
context$1$0.t0 = context$1$0['catch'](8);
if (!(context$1$0.t0.statusCode === 404)) {
context$1$0.next = 19;
break;
}
return context$1$0.abrupt('return', null);
case 19:
case 'end':
return context$1$0.stop();
}
}, null, this, [[8, 15]]);
}
// Retrieve the folder name of the project
function getProjectFolderName(currentPath) {
var findGitFolderRecursively = function findGitFolderRecursively(_x3, _x4) {
var _again = true;
_function: while (_again) {
var projectPath = _x3,
previousFolder = _x4;
_again = false;
var dirs = _fs2['default'].readdirSync(projectPath).filter(function (file) {
return _fs2['default'].statSync(_path2['default'].join(projectPath, file)).isDirectory();
});
var gitMatch = _lodash.find(dirs, function (d) {
return d === '.git';
});
if (gitMatch) {
var gitFolderPath = _path2['default'].join(projectPath, '.git');
if (_fs2['default'].lstatSync(gitFolderPath).isDirectory()) {
return previousFolder;
}
}
var parentFolder = _path2['default'].join(projectPath, '../');
if (parentFolder === '/') {
process.exit(0);
}
_x3 = _path2['default'].join(projectPath, '../');
_x4 = _lodash.last(_lodash.compact(projectPath.split('/')));
_again = true;
dirs = gitMatch = gitFolderPath = parentFolder = undefined;
continue _function;
}
};
return findGitFolderRecursively(currentPath, _lodash.last(currentPath.split('/'))).replace(/[ -]/g, '_');
}
function gh_fetch_issues() {
var issuesOptions = arguments.length <= 0 || arguments[0] === undefined ? { page: 1 } : arguments[0];
var allIssues = arguments.length <= 1 || arguments[1] === undefined ? [] : arguments[1];
var _ref3, repo, client, issues, filteredIssues, issueQuestion, issuesChoices, _allIssues, issueno, issue;
return _regeneratorRuntime.async(function gh_fetch_issues$(context$1$0) {
var _this = this;
while (1) switch (context$1$0.prev = context$1$0.next) {
case 0:
context$1$0.next = 2;
return _regeneratorRuntime.awrap(_utils.gh_helper());
case 2:
_ref3 = context$1$0.sent;
repo = _ref3.repo;
client = _ref3.client;
if (issuesOptions.getFilter) {
context$1$0.next = 15;
break;
}
context$1$0.next = 8;
return _regeneratorRuntime.awrap(_utils.rl.yesOrNoQuestion('Show only issues assigned to you?', 'y'));
case 8:
context$1$0.t0 = context$1$0.sent;
if (!(context$1$0.t0 !== 'n')) {
context$1$0.next = 14;
break;
}
context$1$0.next = 12;
return _regeneratorRuntime.awrap((function callee$1$0() {
var me;
return _regeneratorRuntime.async(function callee$1$0$(context$2$0) {
while (1) switch (context$2$0.prev = context$2$0.next) {
case 0:
context$2$0.next = 2;
return _regeneratorRuntime.awrap(_utils.gh_me(client));
case 2:
me = context$2$0.sent;
issuesOptions.getFilter = function (i) {
return i.assignee && i.assignee.id === me.id;
};
case 4:
case 'end':
return context$2$0.stop();
}
}, null, _this);
})());
case 12:
context$1$0.next = 15;
break;
case 14:
issuesOptions.getFilter = function () {
return function () {
return true;
};
};
case 15:
context$1$0.next = 17;
return _regeneratorRuntime.awrap(_es6Promisify2['default'](repo.issues.bind(repo))(issuesOptions));
case 17:
issues = context$1$0.sent;
filteredIssues = issues.filter(function (i) {
return !i.pull_request;
}).filter(issuesOptions.getFilter);
issueQuestion = undefined;
if (filteredIssues && filteredIssues.length > 0) {
issuesChoices = _utils.issuesLabelMapper(filteredIssues).concat(new _inquirer2['default'].Separator(), {
name: 'Create new issue.',
value: 'y'
}, {
name: 'Show older issue.',
value: 'm'
});
issueQuestion = {
message: 'Which issue?',
type: 'list',
choices: issuesChoices
};
} else {
issueQuestion = {
message: 'No issues found. Create a new one? (y/n)',
'default': 'y'
};
}
_allIssues = allIssues.concat(filteredIssues);
context$1$0.next = 24;
return _regeneratorRuntime.awrap(_utils.rl.question(issueQuestion));
case 24:
issueno = context$1$0.sent;
context$1$0.t1 = issueno;
context$1$0.next = context$1$0.t1 === '' ? 28 : context$1$0.t1 === 'y' ? 30 : context$1$0.t1 === 'm' ? 31 : 35;
break;
case 28:
process.exit(1);
return context$1$0.abrupt('break', 38);
case 30:
return context$1$0.abrupt('return', gh_create_issue());
case 31:
issuesOptions.page = issuesOptions.page + 1;
context$1$0.next = 34;
return _regeneratorRuntime.awrap(gh_fetch_issues(issuesOptions, _allIssues));
case 34:
return context$1$0.abrupt('return', context$1$0.sent);
case 35:
issue = _ramda2['default'].find(function (i) {
return i.number === issueno;
})(_allIssues);
if (!issue) {
_utils.log('error: issue #' + issueno + ' does not exist');
process.exit(1);
}
return context$1$0.abrupt('return', issue);
case 38:
case 'end':
return context$1$0.stop();
}
}, null, this);
}
function gh_feature(_ref4) {
var issueNumber = _ref4.issueNumber;
var issue, inferredBranchPrefix, branchPrefixAnswer, branch_prefix, slug, branchSuffixAnswer, branch_suffix, branch_name;
return _regeneratorRuntime.async(function gh_feature$(context$1$0) {
while (1) switch (context$1$0.prev = context$1$0.next) {
case 0:
context$1$0.next = 2;
return _regeneratorRuntime.awrap(_utils.currBranch());
case 2:
context$1$0.t0 = context$1$0.sent;
if (!(context$1$0.t0 !== 'master')) {
context$1$0.next = 9;
break;
}
context$1$0.next = 6;
return _regeneratorRuntime.awrap(_utils.rl.yesOrNoQuestion('You\'re not on master, are you sure you want to continue?', 'n'));
case 6:
context$1$0.t1 = context$1$0.sent;
if (!(context$1$0.t1 !== 'y')) {
context$1$0.next = 9;
break;
}
process.exit(0);
case 9:
issue = undefined;
if (!_lodash.includes(['c', 'create'], issueNumber)) {
context$1$0.next = 16;
break;
}
context$1$0.next = 13;
return _regeneratorRuntime.awrap(gh_create_issue());
case 13:
issue = context$1$0.sent;
context$1$0.next = 19;
break;
case 16:
context$1$0.next = 18;
return _regeneratorRuntime.awrap(issueNumber ? gh_fetch_issue(issueNumber) : gh_fetch_issues());
case 18:
issue = context$1$0.sent;
case 19:
if (issueNumber && !issue) {
_utils.log('😱 Issue #' + issueNumber + ' does not exist');
process.exit(0);
}
inferredBranchPrefix = getProjectFolderName(process.cwd());
context$1$0.next = 23;
return _regeneratorRuntime.awrap(_utils.rl.question({ message: 'Branch prefix? (\'n\' to leave empty)' }, inferredBranchPrefix));
case 23:
branchPrefixAnswer = context$1$0.sent;
branch_prefix = branchPrefixAnswer !== 'n' ? (branchPrefixAnswer || inferredBranchPrefix) + '-' : '';
slug = _utils.slugify(issue.title);
context$1$0.next = 28;
return _regeneratorRuntime.awrap(_utils.rl.question({ message: 'Branch suffix? (\'n\' to leave empty)' }, slug));
case 28:
branchSuffixAnswer = context$1$0.sent;
branch_suffix = branchSuffixAnswer !== 'n' ? '-' + (branchSuffixAnswer || slug) : '';
branch_name = '' + branch_prefix + issue.number + branch_suffix;
context$1$0.next = 33;
return _regeneratorRuntime.awrap(_utils.exec('git checkout -b ' + branch_name));
case 33:
_utils.log('👍 Created and switched to branch \'' + branch_name + '\'');
context$1$0.next = 36;
return _regeneratorRuntime.awrap(_utils.rl.yesOrNoQuestion('Start tracking time for issue ' + issue.number + '?', 'y'));
case 36:
context$1$0.t2 = context$1$0.sent;
if (!(context$1$0.t2 !== 'n')) {
context$1$0.next = 40;
break;
}
context$1$0.next = 40;
return _regeneratorRuntime.awrap(_toggl.toggl_start());
case 40:
case 'end':
return context$1$0.stop();
}
}, null, this);
}
function gh_pr() {
var _ref5, repo_name, client, repo, branch, issueno, issueRef, issue, pullRequests, pullRequest, pushResult, isWIP, prefix, json, created, customErr;
return _regeneratorRuntime.async(function gh_pr$(context$1$0) {
while (1) switch (context$1$0.prev = context$1$0.next) {
case 0:
context$1$0.next = 2;
return _regeneratorRuntime.awrap(_utils.gh_helper());
case 2:
_ref5 = context$1$0.sent;
repo_name = _ref5.repo_name;
client = _ref5.client;
repo = _ref5.repo;
context$1$0.next = 8;
return _regeneratorRuntime.awrap(_utils.currBranch());
case 8:
branch = context$1$0.sent;
if (branch === 'master') {
_utils.log('Cannot create a PR to merge from master to master, switch branch first');
process.exit(0);
}
issueno = _ramda2['default'].take(2, branch.split('-').map(function (i) {
return parseInt(i, 10);
})).filter(function (x) {
return !!x;
})[0];
if (issueno) {
context$1$0.next = 19;
break;
}
_utils.log('Cannot infer the issue number from the branch name');
_utils.log('Open issues:');
// const issues = await promisify(repo.issues.bind(repo))();
// const issuesChoices = issuesLabelMapper(issues.filter((i) => !i.pull_request));
context$1$0.next = 16;
return _regeneratorRuntime.awrap(_utils.rl.question({ message: 'Which issue?' }));
case 16:
issueno = context$1$0.sent;
context$1$0.next = 20;
break;
case 19:
_utils.log('Inferred issue number ' + issueno + ' from branch name \'' + branch + '\'');
case 20:
issueRef = client.issue(repo_name, issueno);
context$1$0.next = 23;
return _regeneratorRuntime.awrap(_es6Promisify2['default'](issueRef.info.bind(issueRef))());
case 23:
issue = context$1$0.sent;
if (!issue) {
_utils.log('Invalid issue');
}
_utils.log('Issue ' + _utils.issuec('#' + issueno + ': ' + issue.title));
context$1$0.next = 28;
return _regeneratorRuntime.awrap(_es6Promisify2['default'](repo.prs.bind(repo))());
case 28:
pullRequests = context$1$0.sent;
pullRequest = _ramda2['default'].find(function (pr) {
return pr.title.replace(/^\[WIP\]/, '').trim().split(' ')[0] === '#' + issueno + ':';
})(pullRequests);
if (!!pullRequest) {
_utils.log('Existing pull request ' + pullRequest.number + ': ' + pullRequest.title + ' ( ' + pullRequest.html_url + ' )');
} else {
_utils.log('No pull request found for this issue');
}
context$1$0.next = 33;
return _regeneratorRuntime.awrap(_utils.exec('git push -u origin ' + branch));
case 33:
pushResult = context$1$0.sent;
_utils.log(_utils.cmdoutc(pushResult.stdout + '\n' + pushResult.stderr));
if (pullRequest) {
context$1$0.next = 57;
break;
}
context$1$0.next = 38;
return _regeneratorRuntime.awrap(_utils.rl.yesOrNoQuestion('Is this a WIP pull request?', 'n'));
case 38:
context$1$0.t0 = context$1$0.sent;
isWIP = context$1$0.t0 === 'y';
prefix = isWIP ? '[WIP] ' : '';
json = {
title: prefix + '#' + issueno + ': ' + issue.title + ' (closes #' + issueno + ')',
body: 'Issue #' + issueno,
head: repo_name.split('/')[0] + ':' + branch,
base: 'master'
};
context$1$0.prev = 42;
context$1$0.next = 45;
return _regeneratorRuntime.awrap(_es6Promisify2['default'](repo.pr.bind(repo))(json));
case 45:
created = context$1$0.sent;
_utils.log('👍 ' + created.html_url);
context$1$0.next = 49;
return _regeneratorRuntime.awrap(_utils.exec('open "' + created.html_url + '"'));
case 49:
context$1$0.next = 55;
break;
case 51:
context$1$0.prev = 51;
context$1$0.t1 = context$1$0['catch'](42);
customErr = context$1$0.t1.body.errors.filter(function (err) {
return err.code === 'custom';
})[0];
if (!!customErr) {
_utils.log(customErr.message);
} else {
_utils.log('Unexpected error');
}
case 55:
context$1$0.next = 58;
break;
case 57:
_utils.log('Updated existing pull request');
case 58:
case 'end':
return context$1$0.stop();
}
}, null, this, [[42, 51]]);
}
function gh_commit() {
var _ref6, repo_name, client, repo, issues, issuesChoices, issueno, issueRef, issue, message, commitResult;
return _regeneratorRuntime.async(function gh_commit$(context$1$0) {
while (1) switch (context$1$0.prev = context$1$0.next) {
case 0:
context$1$0.next = 2;
return _regeneratorRuntime.awrap(_utils.gh_helper());
case 2:
_ref6 = context$1$0.sent;
repo_name = _ref6.repo_name;
client = _ref6.client;
repo = _ref6.repo;
context$1$0.next = 8;
return _regeneratorRuntime.awrap(_es6Promisify2['default'](repo.issues.bind(repo))());
case 8:
issues = context$1$0.sent;
issuesChoices = _utils.issuesLabelMapper(issues.filter(function (i) {
return !i.pull_request;
}));
context$1$0.next = 12;
return _regeneratorRuntime.awrap(_utils.rl.question({ type: 'list', message: 'Which issue? (number)', choices: issuesChoices }));
case 12:
issueno = context$1$0.sent;
issueRef = client.issue(repo_name, issueno);
context$1$0.next = 16;
return _regeneratorRuntime.awrap(_es6Promisify2['default'](issueRef.info.bind(issueRef))());
case 16:
issue = context$1$0.sent;
if (!issue) {
_utils.log('Invalid issue');
}
message = 'closes #' + issueno + ': ' + issue.title.replace(/^\s*/g, '').replace(/\s*$/g, '').replace('\n', ' ').replace(/["@]/g, '').replace(/`/g, '\\`');
context$1$0.next = 21;
return _regeneratorRuntime.awrap(_utils.rl.yesOrNoQuestion('Commit with the following message:\n' + _utils.issuec(message) + '?', 'y'));
case 21:
context$1$0.t0 = context$1$0.sent;
if (!(context$1$0.t0 === 'y')) {
context$1$0.next = 27;
break;
}
context$1$0.next = 25;
return _regeneratorRuntime.awrap(_utils.execF('git commit -m "' + message + '"'));
case 25:
commitResult = context$1$0.sent;
_utils.log(_utils.cmdoutc(commitResult.stdout + '\n' + commitResult.stderr));
case 27:
case 'end':
return context$1$0.stop();
}
}, null, this);
}
function gh_setup(_ref7) {
var gheAccountName = _ref7.gheAccountName;
var _ref8, githubURL, url, token, fileName;
return _regeneratorRuntime.async(function gh_setup$(context$1$0) {
while (1) switch (context$1$0.prev = context$1$0.next) {
case 0:
_ref8 = _Object$values(_utils.enterpriseRepos).filter(function (_ref9) {
var accountName = _ref9.accountName;
return gheAccountName === accountName;
})[0] || {};
githubURL = _ref8.githubURL;
url = githubURL || 'github.com';
context$1$0.next = 5;
return _regeneratorRuntime.awrap(_utils.rl.question({ message: 'Personal access token: (from https://' + url + '/settings/tokens):' }));
case 5:
token = context$1$0.sent;
if (!token) {
_utils.error('No token inserted.');
process.exit(0);
}
fileName = _utils.gh_token_file_name(gheAccountName);
_fs2['default'].writeFileSync(process.env.HOME + '/' + fileName, token);
case 9:
case 'end':
return context$1$0.stop();
}
}, null, this);
}
exports['default'] = {
gh_commit: gh_commit,
gh_feature: gh_feature,
gh_pr: gh_pr,
gh_setup: gh_setup
};
module.exports = exports['default'];
// FIXME: don't display `n` hint if there aren't anymore issues