var async = require('async'),
base = require('../base'),
hooks = require('../hooks'),
logger = require('../logger'),
openUrl = require('open'),
config = base.getConfig();
function Issue(options) {
if (!options.repo && !options.all) {
logger.error('You must specify a Git repository with a GitHub remote to run this command');
description: 'Provides a set of util commands to work with Issues.',
'milestone': [Number, String],
'number': [String, Array],
'state': ['open', 'closed'],
payload: function (payload, options) {
if (/^\d+$/.test(payload[0])) {
options.number = payload[0];
options.title = options.title || payload[0];
options.message = options.message || payload[1];
Issue.STATE_CLOSED = 'closed';
Issue.STATE_OPEN = 'open';
Issue.prototype.run = function () {
options = instance.options;
options.state = options.state || Issue.STATE_OPEN;
instance.browser(options.user, options.repo, options.number);
hooks.invoke('issue.close', instance, function (afterHooksCallback) {
options.state = Issue.STATE_CLOSED;
logger.log('Closing issue ' + logger.colors.green('#' + options.number) +
' on ' + logger.colors.green(options.user + '/' + options.repo));
instance.close(function (err, issue) {
logger.error('Can\'t close issue.');
logger.log(issue.html_url);
logger.log('Adding comment on issue ' + logger.colors.green('#' + options.number));
instance.comment(function (err, issue) {
logger.error('Can\'t add comment.');
logger.log(issue.html_url);
logger.log('Listing ' + logger.colors.green(options.state) + ' issues for ' +
logger.colors.green(options.user));
instance.listFromAllRepositories(function (err) {
logger.error('Can\'t list issues for ' + options.user + '.');
logger.log('Listing ' + logger.colors.green(options.state) +
' issues on ' + logger.colors.green(options.user + '/' + options.repo));
instance.list(options.user, options.repo, function (err) {
logger.error('Can\'t list issues on ' + options.user + '/' + options.repo);
hooks.invoke('issue.new', instance, function (afterHooksCallback) {
logger.log('Creating a new issue on ' + logger.colors.green(options.user + '/' + options.repo));
instance.new(function (err, issue) {
logger.error('Can\'t create new issue.');
options.number = issue.number;
logger.log(issue.html_url);
hooks.invoke('issue.open', instance, function (afterHooksCallback) {
logger.log('Opening issue ' + logger.colors.green('#' + options.number) +
' on ' + logger.colors.green(options.user + '/' + options.repo));
instance.open(function (err, issue) {
logger.error('Can\'t open issue.');
logger.log(issue.html_url);
Issue.prototype.browser = function (user, repo, number) {
openUrl('https://github.com/' + user + '/' + repo + '/issues/' + number);
Issue.prototype.close = function (opt_callback) {
instance.getIssue_(function (err, issue) {
opt_callback && opt_callback(err);
instance.editIssue_(issue.title, Issue.STATE_CLOSED, opt_callback);
Issue.prototype.comment = function (opt_callback) {
options = instance.options,
body = logger.applyReplacements(options.comment, config.replace) + config.signature;
base.github.issues.createComment(payload, opt_callback);
Issue.prototype.editIssue_ = function (title, state, opt_callback) {
options = instance.options,
options.label = options.label || [];
assignee: options.assignee,
milestone: options.milestone,
base.github.issues.edit(payload, opt_callback);
Issue.prototype.getIssue_ = function (opt_callback) {
options = instance.options,
base.github.issues.getRepoIssue(payload, opt_callback);
Issue.prototype.list = function (user, repo, opt_callback) {
options = instance.options,
options.label = options.label || '';
if (options['no-milestone']) {
payload.milestone = 'none';
} else if (options.milestone) {
payload.milestone = options.milestone;
operations.push(function (callback) {
base.github.issues.getAllMilestones({
}, function (err, results) {
logger.warn(err.message);
results.some(function (milestone) {
if (options.milestone === milestone.title) {
logger.debug('Milestone ' + milestone.title + ' number: ' + milestone.number);
payload.milestone = milestone.number;
payload.assignee = options.assignee;
operations.push(function (callback) {
base.github.issues.repoIssues(payload, callback);
async.series(operations, function (err, results) {
if (err && !options.all) {
logger.error(logger.getErrorMessage(err));
results.forEach(function (result) {
issues = issues.concat(result);
issues.sort(function (a, b) {
return a.number > b.number ? -1 : 1;
if (issues && issues.length > 0) {
issues.forEach(function (issue) {
var labels = issue.label || [];
logger.log(logger.colors.green('#' + issue.number) + ' ' + issue.title + ' ' +
logger.colors.magenta('@' + issue.user.login + ' (' + logger.getDuration(issues.created_at) + ')'));
labels.forEach(function (label) {
logger.log(logger.colors.green('label: ') + labels.join(', '));
logger.log(logger.colors.green('milestone: ') +
issue.milestone.title + ' - ' + issue.milestone.number);
logger.log(logger.colors.blue(issue.html_url));
opt_callback && opt_callback(err);
Issue.prototype.listFromAllRepositories = function (opt_callback) {
options = instance.options,
base.github.repos.getAll(payload, function (err, repositories) {
opt_callback && opt_callback(err);
repositories.forEach(function (repository) {
instance.list(repository.owner.login, repository.name, opt_callback);
Issue.prototype.new = function (opt_callback) {
options = instance.options,
body = logger.applyReplacements(options.message, config.replace);
options.label = options.label.split(',');
assignee: options.assignee,
base.github.issues.create(payload, opt_callback);
Issue.prototype.open = function (opt_callback) {
instance.getIssue_(function (err, issue) {
opt_callback && opt_callback(err);
instance.editIssue_(issue.title, Issue.STATE_OPEN, opt_callback);