├── .gitignore ├── .vscode ├── database.json └── settings.json ├── __tests__ └── index.spec.js ├── types └── wide-align.js ├── .npmignore ├── src ├── types │ ├── wide-align.d.ts │ ├── chalk.d.ts │ └── cfonts.d.ts ├── lib │ ├── tools │ │ ├── spinner.ts │ │ ├── promiseCompose.ts │ │ ├── hyperlinker.ts │ │ ├── createshell.ts │ │ ├── tableShow.ts │ │ ├── cfonts.ts │ │ ├── output.ts │ │ ├── saveInfo.ts │ │ ├── askQuestion.ts │ │ ├── verification.ts │ │ └── request.ts │ └── action │ │ ├── users.ts │ │ ├── search.ts │ │ ├── reaction.ts │ │ └── issues.ts └── bin │ ├── tools │ └── output.ts │ ├── github_search.ts │ ├── github_reaction.ts │ ├── github_users.ts │ ├── index.ts │ ├── github_issues.ts │ ├── github_pullrequest.ts │ └── github_repos.ts ├── lib ├── tools │ ├── spinner.js │ ├── promiseCompose.js │ ├── hyperlinker.js │ ├── cfonts.js │ ├── tableShow.js │ ├── createshell.js │ ├── output.js │ ├── askQuestion.js │ ├── saveInfo.js │ ├── request.js │ └── verification.js └── action │ ├── users.js │ └── search.js ├── tsconfig.json ├── tslint.json ├── bin ├── tools │ └── output.js ├── github_search.js ├── index.js ├── github_reaction.js ├── github_users.js ├── github_issues.js ├── github_pullrequest.js └── github_repos.js ├── package.json ├── description.md └── README.md /.gitignore: -------------------------------------------------------------------------------- 1 | /node_modules -------------------------------------------------------------------------------- /.vscode/database.json: -------------------------------------------------------------------------------- 1 | {} -------------------------------------------------------------------------------- /__tests__/index.spec.js: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /types/wide-align.js: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /.npmignore: -------------------------------------------------------------------------------- 1 | __tests__ 2 | description.md -------------------------------------------------------------------------------- /.vscode/settings.json: -------------------------------------------------------------------------------- 1 | { 2 | "git.ignoreLimitWarning": true 3 | } -------------------------------------------------------------------------------- /src/types/wide-align.d.ts: -------------------------------------------------------------------------------- 1 | declare module 'wide-align' { 2 | export function center (message: string, width: number): string 3 | } -------------------------------------------------------------------------------- /src/lib/tools/spinner.ts: -------------------------------------------------------------------------------- 1 | // loading 模块 2 | import * as ora from 'ora' 3 | 4 | const spinner = ora('task processing') 5 | 6 | export default spinner 7 | -------------------------------------------------------------------------------- /lib/tools/spinner.js: -------------------------------------------------------------------------------- 1 | "use strict"; 2 | Object.defineProperty(exports, "__esModule", { value: true }); 3 | // loading 模块 4 | var ora = require("ora"); 5 | var spinner = ora('task processing'); 6 | exports.default = spinner; 7 | -------------------------------------------------------------------------------- /src/lib/tools/promiseCompose.ts: -------------------------------------------------------------------------------- 1 | const promiseCompose = function (fns: Array) { 2 | let initialPromise = fns.shift() 3 | return fns.reduce(function (prefn, currfn): Function { 4 | return prefn.then(currfn) 5 | }, initialPromise()).catch((err: any) => { 6 | console.log(err) 7 | }) 8 | } 9 | export default promiseCompose 10 | -------------------------------------------------------------------------------- /src/types/chalk.d.ts: -------------------------------------------------------------------------------- 1 | declare module 'chalk' { 2 | export function magentaBright (message: string): string 3 | export function yellow (message: string): string 4 | export function blue (message: string): string 5 | export function green (message: string): string 6 | export function cyan (message: string): string 7 | export function red (message: string): string 8 | } 9 | -------------------------------------------------------------------------------- /src/types/cfonts.d.ts: -------------------------------------------------------------------------------- 1 | interface cfontsOptions { 2 | font?: string; 3 | align?: string; 4 | colors?: Array; 5 | background?: string; 6 | letterSpacing?: number; 7 | lineHeight?: number; 8 | space?: boolean; 9 | maxLength?: string; 10 | } 11 | 12 | declare module 'cfonts' { 13 | export function say (text: string, textOptions: cfontsOptions) : void 14 | } -------------------------------------------------------------------------------- /src/lib/tools/hyperlinker.ts: -------------------------------------------------------------------------------- 1 | const supportsHyperlinks = require('supports-hyperlinks') 2 | const hyperlinker = require('hyperlinker') 3 | 4 | export default function getHyperlinkText(theurl: string): string { 5 | let thedata = '' 6 | if (supportsHyperlinks.stdout) { 7 | thedata = hyperlinker('点击查看详情', theurl) 8 | } else { 9 | thedata = theurl 10 | } 11 | return thedata 12 | } -------------------------------------------------------------------------------- /lib/tools/promiseCompose.js: -------------------------------------------------------------------------------- 1 | "use strict"; 2 | Object.defineProperty(exports, "__esModule", { value: true }); 3 | var promiseCompose = function (fns) { 4 | var initialPromise = fns.shift(); 5 | return fns.reduce(function (prefn, currfn) { 6 | return prefn.then(currfn); 7 | }, initialPromise()).catch(function (err) { 8 | console.log(err); 9 | }); 10 | }; 11 | exports.default = promiseCompose; 12 | -------------------------------------------------------------------------------- /src/lib/tools/createshell.ts: -------------------------------------------------------------------------------- 1 | import { exec } from 'child_process' 2 | 3 | export default function shell (commandstring: string, fn?: Function) { 4 | exec(commandstring, function (err, stdout, stderr) { 5 | if (err) { 6 | console.log(err) 7 | process.exit() 8 | } 9 | if (stdout) { 10 | console.log(stdout.toString()) 11 | fn&&fn(stdout.toString()) 12 | return 13 | } 14 | if (stderr) { 15 | process.exit() 16 | } 17 | }) 18 | } -------------------------------------------------------------------------------- /lib/tools/hyperlinker.js: -------------------------------------------------------------------------------- 1 | "use strict"; 2 | Object.defineProperty(exports, "__esModule", { value: true }); 3 | var supportsHyperlinks = require('supports-hyperlinks'); 4 | var hyperlinker = require('hyperlinker'); 5 | function getHyperlinkText(theurl) { 6 | var thedata = ''; 7 | if (supportsHyperlinks.stdout) { 8 | thedata = hyperlinker('点击查看详情', theurl); 9 | } 10 | else { 11 | thedata = theurl; 12 | } 13 | return thedata; 14 | } 15 | exports.default = getHyperlinkText; 16 | -------------------------------------------------------------------------------- /lib/tools/cfonts.js: -------------------------------------------------------------------------------- 1 | "use strict"; 2 | Object.defineProperty(exports, "__esModule", { value: true }); 3 | var cfonts = require("cfonts"); 4 | function textFonts(text, textOptions) { 5 | cfonts.say(text, Object.assign({ 6 | font: '3d', 7 | align: 'center', 8 | colors: ['magenta'], 9 | background: 'black', 10 | letterSpacing: 1, 11 | lineHeight: 0.00005, 12 | space: true, 13 | maxLength: '0' 14 | }, textOptions || {})); 15 | } 16 | exports.default = textFonts; 17 | -------------------------------------------------------------------------------- /src/lib/tools/tableShow.ts: -------------------------------------------------------------------------------- 1 | import * as Table from 'cli-table2' 2 | 3 | // 创建一个表格实例 4 | export default function createTable (createOptions?: any) { 5 | return new Table(Object.assign({ 6 | chars: { 'top': '═' , 'top-mid': '╤' , 'top-left': '╔' , 'top-right': '╗' 7 | , 'bottom': '═' , 'bottom-mid': '╧' , 'bottom-left': '╚' , 'bottom-right': '╝' 8 | , 'left': '║' , 'left-mid': '╟' , 'mid': '─' , 'mid-mid': '┼' 9 | , 'right': '║' , 'right-mid': '╢' , 'middle': '│' } 10 | }, createOptions || {})) 11 | } -------------------------------------------------------------------------------- /tsconfig.json: -------------------------------------------------------------------------------- 1 | { 2 | "compilerOptions": { 3 | "module": "commonjs", 4 | "target": "es5", 5 | "noImplicitAny": true, 6 | "removeComments": false, 7 | "preserveConstEnums": true, 8 | "outDir": "./", 9 | "sourceMap": false, 10 | "moduleResolution": "node", 11 | "lib": ["dom", "es2016"] 12 | }, 13 | "compileOnSave": true, 14 | "include": [ 15 | "src/**/*" 16 | ], 17 | "exclude": [ 18 | "node_modules", 19 | "**/*.spec.ts" 20 | ] 21 | } -------------------------------------------------------------------------------- /lib/tools/tableShow.js: -------------------------------------------------------------------------------- 1 | "use strict"; 2 | Object.defineProperty(exports, "__esModule", { value: true }); 3 | var Table = require("cli-table2"); 4 | // 创建一个表格实例 5 | function createTable(createOptions) { 6 | return new Table(Object.assign({ 7 | chars: { 'top': '═', 'top-mid': '╤', 'top-left': '╔', 'top-right': '╗', 8 | 'bottom': '═', 'bottom-mid': '╧', 'bottom-left': '╚', 'bottom-right': '╝', 9 | 'left': '║', 'left-mid': '╟', 'mid': '─', 'mid-mid': '┼', 10 | 'right': '║', 'right-mid': '╢', 'middle': '│' } 11 | }, createOptions || {})); 12 | } 13 | exports.default = createTable; 14 | -------------------------------------------------------------------------------- /lib/tools/createshell.js: -------------------------------------------------------------------------------- 1 | "use strict"; 2 | Object.defineProperty(exports, "__esModule", { value: true }); 3 | var child_process_1 = require("child_process"); 4 | function shell(commandstring, fn) { 5 | child_process_1.exec(commandstring, function (err, stdout, stderr) { 6 | if (err) { 7 | console.log(err); 8 | process.exit(); 9 | } 10 | if (stdout) { 11 | console.log(stdout.toString()); 12 | fn && fn(stdout.toString()); 13 | return; 14 | } 15 | if (stderr) { 16 | process.exit(); 17 | } 18 | }); 19 | } 20 | exports.default = shell; 21 | -------------------------------------------------------------------------------- /src/lib/tools/cfonts.ts: -------------------------------------------------------------------------------- 1 | import * as cfonts from 'cfonts' 2 | interface cfontsOptions { 3 | font?: string; 4 | align?: string; 5 | colors?: Array; 6 | background?: string; 7 | letterSpacing?: number; 8 | lineHeight?: number; 9 | space?: boolean; 10 | maxLength?: string; 11 | } 12 | export default function textFonts (text: string, textOptions?: cfontsOptions) { 13 | cfonts.say(text, Object.assign({ 14 | font: '3d', 15 | align: 'center', 16 | colors: ['magenta'], 17 | background: 'black', 18 | letterSpacing: 1, 19 | lineHeight: 0.00005, 20 | space: true, 21 | maxLength: '0' 22 | }, textOptions || {})) 23 | } -------------------------------------------------------------------------------- /tslint.json: -------------------------------------------------------------------------------- 1 | { 2 | "extends": "tslint:recommended", 3 | "rulesDirectory": ["path/to/custom/rules/directory/", "another/path/"], 4 | "rules": { 5 | "max-line-length": { 6 | "options": [120] 7 | }, 8 | "new-parens": true, 9 | "no-arg": true, 10 | "no-bitwise": true, 11 | "no-conditional-assignment": true, 12 | "no-consecutive-blank-lines": false, 13 | "no-console": { 14 | "severity": "warning", 15 | "options": [ 16 | "debug", 17 | "info", 18 | "log", 19 | "time", 20 | "timeEnd", 21 | "trace" 22 | ] 23 | } 24 | }, 25 | "jsRules": { 26 | "max-line-length": { 27 | "options": [120] 28 | } 29 | } 30 | } -------------------------------------------------------------------------------- /bin/tools/output.js: -------------------------------------------------------------------------------- 1 | "use strict"; 2 | Object.defineProperty(exports, "__esModule", { value: true }); 3 | var chalk = require("chalk"); 4 | exports.mainTitle = function (message) { 5 | console.log(chalk.magentaBright(' ' + message)); 6 | }; 7 | exports.subTitle = function (message) { 8 | console.log('\n' + chalk.yellow(' ' + message)); 9 | }; 10 | exports.describe = function (message) { 11 | console.log(message ? chalk.blue(' ' + message) : ''); 12 | }; 13 | exports.command = function (message) { 14 | console.log(message ? chalk.cyan(' ' + message) : ''); 15 | }; 16 | exports.example = function (message) { 17 | console.log(message ? chalk.yellow(' ' + message) : ''); 18 | }; 19 | exports.success = function (message) { 20 | console.log(chalk.green(message)); 21 | }; 22 | exports.error = function (message) { 23 | console.log(chalk.red(message)); 24 | }; 25 | exports.info = function (message) { 26 | console.log(chalk.cyan(message)); 27 | }; 28 | -------------------------------------------------------------------------------- /lib/tools/output.js: -------------------------------------------------------------------------------- 1 | "use strict"; 2 | Object.defineProperty(exports, "__esModule", { value: true }); 3 | var chalk = require("chalk"); 4 | exports.mainTitle = function (message) { 5 | console.log(chalk.magentaBright(' ' + message)); 6 | }; 7 | exports.subTitle = function (message) { 8 | console.log('\n' + chalk.yellow(' ' + message)); 9 | }; 10 | exports.describe = function (message) { 11 | console.log(message ? chalk.blue(' ' + message) : ''); 12 | }; 13 | exports.command = function (message) { 14 | console.log(message ? chalk.cyan(' ' + message) : ''); 15 | }; 16 | exports.example = function (message) { 17 | console.log(message ? chalk.yellow(' ' + message) : ''); 18 | }; 19 | exports.success = function (message) { 20 | console.log(chalk.green(message)); 21 | }; 22 | exports.error = function (message) { 23 | console.log(chalk.red(message)); 24 | }; 25 | exports.info = function (message) { 26 | console.log(chalk.cyan(message)); 27 | }; 28 | -------------------------------------------------------------------------------- /src/bin/tools/output.ts: -------------------------------------------------------------------------------- 1 | import * as chalk from 'chalk' 2 | 3 | export const mainTitle = function (message: string) { 4 | console.log(chalk.magentaBright(' ' + message)) 5 | } 6 | 7 | export const subTitle = function (message: string) { 8 | console.log('\n' + chalk.yellow(' ' + message)) 9 | } 10 | 11 | export const describe = function (message?: string) { 12 | console.log(message ? chalk.blue(' ' + message) : '') 13 | } 14 | 15 | export const command = function (message?: string) { 16 | console.log(message ? chalk.cyan(' ' + message) : '') 17 | } 18 | 19 | export const example = function (message?: string) { 20 | console.log(message ? chalk.yellow(' ' + message) : '') 21 | } 22 | 23 | export const success = function (message: string) { 24 | console.log(chalk.green(message)) 25 | } 26 | 27 | export const error = function (message: string) { 28 | console.log(chalk.red(message)) 29 | } 30 | 31 | export const info = function (message: string) { 32 | console.log(chalk.cyan(message)) 33 | } -------------------------------------------------------------------------------- /src/lib/tools/output.ts: -------------------------------------------------------------------------------- 1 | import * as chalk from 'chalk' 2 | 3 | export const mainTitle = function (message: string) { 4 | console.log(chalk.magentaBright(' ' + message)) 5 | } 6 | 7 | export const subTitle = function (message: string) { 8 | console.log('\n' + chalk.yellow(' ' + message)) 9 | } 10 | 11 | export const describe = function (message?: string) { 12 | console.log(message ? chalk.blue(' ' + message) : '') 13 | } 14 | 15 | export const command = function (message?: string) { 16 | console.log(message ? chalk.cyan(' ' + message) : '') 17 | } 18 | 19 | export const example = function (message?: string) { 20 | console.log(message ? chalk.yellow(' ' + message) : '') 21 | } 22 | 23 | export const success = function (message: string) { 24 | console.log(chalk.green(message)) 25 | } 26 | 27 | export const error = function (message: string) { 28 | console.log(chalk.red(message)) 29 | } 30 | 31 | export const info = function (message: string) { 32 | console.log(chalk.cyan(message)) 33 | } -------------------------------------------------------------------------------- /src/bin/github_search.ts: -------------------------------------------------------------------------------- 1 | #! /usr/bin/env node 2 | 3 | import * as program from 'commander' 4 | 5 | import { mainTitle, command, example, describe, error } from '../lib/tools/output' 6 | import { searchStrategy } from '../lib/action/search' 7 | 8 | program 9 | .version(require('../package.json').version) 10 | .option('-h', 'get help') 11 | .parse(process.argv) 12 | 13 | const commandTypeObject: {[key: string]: string} = { 14 | 'r': 'search repositories', 15 | 'c': 'search commits', 16 | 'i': 'search issues', 17 | 'u': 'search users' 18 | } 19 | 20 | program.on('--help', function () { 21 | mainTitle('Commands:') 22 | command() 23 | Object.keys(commandTypeObject).forEach((item: any) => { 24 | command(`$ gh sr ${item} --- ${commandTypeObject[item]}`) 25 | }) 26 | mainTitle('use examples:') 27 | example() 28 | describe('search repositories:') 29 | example('$ gh sr -r') 30 | example() 31 | }) 32 | 33 | let paramArray = process.argv.slice(2) 34 | let thecmd = paramArray[0] // 命令类型 35 | 36 | if (!thecmd || thecmd === '-h') { 37 | program.help() 38 | } 39 | 40 | if (!commandTypeObject.hasOwnProperty(thecmd)) { 41 | error('the option you input is invalid, you could get the surpported options through $ gh sr -h') 42 | process.exit() 43 | } 44 | 45 | searchStrategy[thecmd]() -------------------------------------------------------------------------------- /bin/github_search.js: -------------------------------------------------------------------------------- 1 | #! /usr/bin/env node 2 | "use strict"; 3 | Object.defineProperty(exports, "__esModule", { value: true }); 4 | var program = require("commander"); 5 | var output_1 = require("../lib/tools/output"); 6 | var search_1 = require("../lib/action/search"); 7 | program 8 | .version(require('../package.json').version) 9 | .option('-h', 'get help') 10 | .parse(process.argv); 11 | var commandTypeObject = { 12 | 'r': 'search repositories', 13 | 'c': 'search commits', 14 | 'i': 'search issues', 15 | 'u': 'search users' 16 | }; 17 | program.on('--help', function () { 18 | output_1.mainTitle('Commands:'); 19 | output_1.command(); 20 | Object.keys(commandTypeObject).forEach(function (item) { 21 | output_1.command("$ gh sr " + item + " --- " + commandTypeObject[item]); 22 | }); 23 | output_1.mainTitle('use examples:'); 24 | output_1.example(); 25 | output_1.describe('search repositories:'); 26 | output_1.example('$ gh sr -r'); 27 | output_1.example(); 28 | }); 29 | var paramArray = process.argv.slice(2); 30 | var thecmd = paramArray[0]; // 命令类型 31 | if (!thecmd || thecmd === '-h') { 32 | program.help(); 33 | } 34 | if (!commandTypeObject.hasOwnProperty(thecmd)) { 35 | output_1.error('the option you input is invalid, you could get the surpported options through $ gh sr -h'); 36 | process.exit(); 37 | } 38 | search_1.searchStrategy[thecmd](); 39 | -------------------------------------------------------------------------------- /src/lib/tools/saveInfo.ts: -------------------------------------------------------------------------------- 1 | import { homedir } from 'os' 2 | import { writeFile, readFile, existsSync } from 'fs' 3 | 4 | const theHomeDir = homedir() // home目录路径 5 | const configFilePath = `${theHomeDir}/githubUserInfo.json` 6 | 7 | // 获取信息 8 | export const getInfo = function () { 9 | return (new Promise(function (resolve, reject) { 10 | if (!existsSync(configFilePath)) { 11 | resolve({}) 12 | } else { 13 | readFile(configFilePath, function (err, data) { 14 | if (err) { 15 | reject(err) 16 | return 17 | } 18 | let filedata = data.toString() 19 | while (typeof filedata === 'string') { 20 | filedata = JSON.parse(filedata) 21 | } 22 | resolve(filedata) 23 | }) 24 | } 25 | })).catch((err: any) => { 26 | console.log(err) 27 | }) 28 | } 29 | 30 | // 存储信息 31 | export const saveInfo = function (saveOptions: any) { 32 | let writedata = JSON.stringify(saveOptions) 33 | return (new Promise(function (resolve, reject) { 34 | function filecb (err: any) { 35 | if (err) { 36 | reject(err) 37 | } 38 | resolve() 39 | } 40 | if (!existsSync(configFilePath)) { 41 | writeFile.call(this, configFilePath, writedata, filecb) 42 | } else { 43 | getInfo().then(function (res) { 44 | let thewritedata = JSON.stringify(Object.assign(res, saveOptions)) 45 | writeFile.call(this, configFilePath, thewritedata, filecb) 46 | }) 47 | } 48 | })).catch((err: any) => { 49 | console.log(err) 50 | }) 51 | } -------------------------------------------------------------------------------- /lib/tools/askQuestion.js: -------------------------------------------------------------------------------- 1 | "use strict"; 2 | // 提问交互模块 3 | Object.defineProperty(exports, "__esModule", { value: true }); 4 | var inquire = require("inquirer"); 5 | var stringz_1 = require("stringz"); 6 | var wide_align_1 = require("wide-align"); 7 | var chalk_1 = require("chalk"); 8 | var width_rule = { 9 | number: 10, 10 | title: 20, 11 | description: 40, 12 | url: 60 13 | }; 14 | exports.createChoiceTable = function (heads, datalist) { 15 | var tableWidth = 0; 16 | heads.forEach(function (item) { 17 | tableWidth += width_rule[item.type]; 18 | }); 19 | var header = "\u2502" + heads.map(function (item) { 20 | return wide_align_1.center(chalk_1.yellow(item.value), width_rule[item.type]); 21 | }).join('│') + "\u2502"; 22 | var choices = [ 23 | new inquire.Separator("" + stringz_1.limit('', tableWidth, '─')), 24 | new inquire.Separator(header), 25 | new inquire.Separator("" + stringz_1.limit('', tableWidth, '─')) 26 | ]; 27 | datalist.forEach(function (item) { 28 | choices.push("\u2502" + item.map(function (item, index) { 29 | return wide_align_1.center(item, width_rule[heads[index].type]); 30 | }).join('│') + "\u2502"); 31 | choices.push(new inquire.Separator("" + stringz_1.limit('', tableWidth, '─'))); 32 | }); 33 | return choices; 34 | }; 35 | function askquestion(question, resolve) { 36 | return inquire.prompt(question).then(function (answers) { 37 | resolve(answers); 38 | }).catch(function (err) { 39 | console.log(err); 40 | process.exit(); 41 | }); 42 | } 43 | exports.default = askquestion; 44 | -------------------------------------------------------------------------------- /lib/tools/saveInfo.js: -------------------------------------------------------------------------------- 1 | "use strict"; 2 | Object.defineProperty(exports, "__esModule", { value: true }); 3 | var os_1 = require("os"); 4 | var fs_1 = require("fs"); 5 | var theHomeDir = os_1.homedir(); // home目录路径 6 | var configFilePath = theHomeDir + "/githubUserInfo.json"; 7 | // 获取信息 8 | exports.getInfo = function () { 9 | return (new Promise(function (resolve, reject) { 10 | fs_1.readFile(configFilePath, function (err, data) { 11 | if (err) { 12 | reject(err); 13 | } 14 | var filedata = data.toString(); 15 | while (typeof filedata === 'string') { 16 | filedata = JSON.parse(filedata); 17 | } 18 | resolve(filedata); 19 | }); 20 | })).catch(function (err) { 21 | console.log(err); 22 | }); 23 | }; 24 | // 存储信息 25 | exports.saveInfo = function (saveOptions) { 26 | var writedata = JSON.stringify(saveOptions); 27 | return (new Promise(function (resolve, reject) { 28 | function filecb(err) { 29 | if (err) { 30 | reject(err); 31 | } 32 | resolve(); 33 | } 34 | if (!fs_1.existsSync(configFilePath)) { 35 | fs_1.writeFile.call(this, configFilePath, writedata, filecb); 36 | } 37 | else { 38 | exports.getInfo().then(function (res) { 39 | var thewritedata = JSON.stringify(Object.assign(res, saveOptions)); 40 | fs_1.writeFile.call(this, configFilePath, thewritedata, filecb); 41 | }); 42 | } 43 | })).catch(function (err) { 44 | console.log(err); 45 | }); 46 | }; 47 | -------------------------------------------------------------------------------- /src/lib/tools/askQuestion.ts: -------------------------------------------------------------------------------- 1 | // 提问交互模块 2 | 3 | import * as inquire from 'inquirer' 4 | import {limit} from 'stringz' 5 | import { center } from 'wide-align' 6 | import {yellow} from 'chalk' 7 | 8 | const width_rule: any = { 9 | number: 10, 10 | title: 20, 11 | description: 40, 12 | url: 60 13 | } 14 | 15 | interface questionObject { 16 | type: string; 17 | name: string; 18 | message: any; 19 | default?: any; 20 | choices?: any; 21 | validate?: any; 22 | filter?: any; 23 | when?: any; 24 | pageSize?: number; 25 | prefix?: string; 26 | suffix?: string; 27 | } 28 | 29 | export const createChoiceTable = function (heads: any, datalist: any) { 30 | let tableWidth: number = 0 31 | heads.forEach((item: any) => { 32 | tableWidth += width_rule[item.type] 33 | }) 34 | const header = `│${heads.map((item: any) => { 35 | return center(yellow(item.value), width_rule[item.type]) 36 | }).join('│')}│` 37 | let choices: Array = [ 38 | new inquire.Separator(`${limit('', tableWidth, '─')}`), 39 | new inquire.Separator(header), 40 | new inquire.Separator(`${limit('', tableWidth, '─')}`) 41 | ] 42 | datalist.forEach((item: any) => { 43 | choices.push(`│${item.map((item: any, index: number) => { 44 | return center(item, width_rule[heads[index].type]) 45 | }).join('│')}│`) 46 | choices.push(new inquire.Separator(`${limit('', tableWidth, '─')}`)) 47 | }) 48 | return choices 49 | } 50 | 51 | export default function askquestion (question: Array, resolve: any) { 52 | return inquire.prompt(question).then((answers: any) => { 53 | resolve(answers) 54 | }).catch((err: any) => { 55 | console.log(err) 56 | process.exit() 57 | }) 58 | } 59 | -------------------------------------------------------------------------------- /package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "@mozheng-neal/github_cli", 3 | "version": "1.0.9", 4 | "description": "github的命令行操作工具", 5 | "scripts": { 6 | "test": "jest --watch", 7 | "build": "./node_modules/.bin/tsc", 8 | "watch": "./node_modules/.bin/tsc -w", 9 | "clean": "rm -rf bin lib", 10 | "prepublish": "npm run clean && npm run build" 11 | }, 12 | "bin": { 13 | "gh": "./bin/index.js", 14 | "gh_rs": "./bin/github_repos.js", 15 | "gh_iu": "./bin/github_issues.js", 16 | "gh_pr": "./bin/github_pullrequest.js", 17 | "gh_rt": "./bin/github_reaction.js", 18 | "gh_us": "./bin/github_users.js", 19 | "gh_sr": "./bin/github_search.js" 20 | }, 21 | "dependencies": { 22 | "axios": "^0.17.1", 23 | "chalk": "^2.3.1", 24 | "cli-progress": "^1.7.0", 25 | "commander": "^2.14.1", 26 | "hyperlinker": "^1.0.0", 27 | "node-emoji": "^1.8.1", 28 | "ora": "^1.4.0", 29 | "request-promise": "^4.2.2", 30 | "stringz": "^0.4.0", 31 | "supports-hyperlinks": "^1.0.1", 32 | "update-notifier": "^2.3.0", 33 | "wide-align": "^1.1.2", 34 | "cfonts": "^1.1.3", 35 | "cli-table2": "^0.2.0", 36 | "inquirer": "^4.0.2" 37 | }, 38 | "repository": { 39 | "type": "git", 40 | "url": "git+https://github.com/NealST/github-cli.git" 41 | }, 42 | "keywords": [ 43 | "github", 44 | "cli" 45 | ], 46 | "author": "mozheng-neal", 47 | "license": "ISC", 48 | "bugs": { 49 | "url": "https://github.com/NealST/github-cli/issues" 50 | }, 51 | "homepage": "https://github.com/NealST/github-cli#readme", 52 | "devDependencies": { 53 | "@types/chalk": "^2.2.0", 54 | "@types/cli-table2": "^0.2.1", 55 | "@types/commander": "^2.12.2", 56 | "@types/inquirer": "0.0.36", 57 | "@types/node": "^8.9.4", 58 | "@types/ora": "^1.3.2", 59 | "@types/update-notifier": "^2.0.0", 60 | "@types/node-emoji": "^1.8.0", 61 | "jest": "^22.4.2", 62 | "tslint": "^5.9.1", 63 | "typescript": "^2.7.2", 64 | "webpack": "^3.11.0" 65 | } 66 | } 67 | -------------------------------------------------------------------------------- /lib/tools/request.js: -------------------------------------------------------------------------------- 1 | "use strict"; 2 | // 客户端请求模块 3 | var __assign = (this && this.__assign) || Object.assign || function(t) { 4 | for (var s, i = 1, n = arguments.length; i < n; i++) { 5 | s = arguments[i]; 6 | for (var p in s) if (Object.prototype.hasOwnProperty.call(s, p)) 7 | t[p] = s[p]; 8 | } 9 | return t; 10 | }; 11 | Object.defineProperty(exports, "__esModule", { value: true }); 12 | var axios = require('axios'); 13 | var output_1 = require("./output"); 14 | exports.thedomain = 'https://api.github.com'; 15 | exports.previewAccept = 'application/vnd.github.mercy-preview+json'; 16 | // 客户端请求封装 17 | exports.request = function (url, type, data, requestOptions) { 18 | var ajaxdata = type === 'get' ? { params: data } : { data: data, withCredentials: true }; 19 | var configOptions = Object.assign(__assign({ url: "" + exports.thedomain + url, method: type, timeout: 5000 }, ajaxdata), requestOptions || {}); 20 | // there are some problems with axios promise, so I wrapped a new promise 21 | return (new Promise(function (resolve, reject) { 22 | axios(configOptions).catch(function (err) { 23 | reject(err); 24 | }).then(function (res) { 25 | // 备注,star仓库等操作成功后也会返回204 26 | if (res.status === 204 && process.env.githubActionType === 'remove') { 27 | output_1.success('delete success!'); 28 | } 29 | resolve(res); 30 | }); 31 | })).catch(function (err) { 32 | if (err.response && err.response.status === 404 && process.argv.indexOf('ck') > 0) { 33 | output_1.error('this user is not a collaborator!'); 34 | return; 35 | } 36 | if (err.response && err.response.data) { 37 | output_1.error(err.response.statusText); 38 | output_1.error(err.response.data.message); 39 | /*if (err.response.status === 401) { 40 | error('you are unauthorized') 41 | } 42 | if (err.response.status === 403) { 43 | error('your authentication is forbidden') 44 | } 45 | if (err.response.status === 410) { 46 | error('current action is disabled or deprecated') 47 | } 48 | if (err.response.status === 422) { 49 | error('unprocessable request,maybe the data you input is invalid') 50 | } 51 | if (err.response.status === 405)*/ 52 | // 有些查看操作,checkcolloborators如果结果为否会返回404 53 | } 54 | if (err.message === 'timeout of 5000ms exceeded') { 55 | output_1.error('request timeout,please try again'); 56 | } 57 | process.exit(); 58 | }); 59 | }; 60 | -------------------------------------------------------------------------------- /src/bin/github_reaction.ts: -------------------------------------------------------------------------------- 1 | #! /usr/bin/env node 2 | 3 | import * as program from 'commander' 4 | 5 | import { mainTitle, command, example, describe, error } from '../lib/tools/output' 6 | import { reactionStrategy } from '../lib/action/reaction' 7 | 8 | program 9 | .version(require('../package.json').version) 10 | .option('-h', 'get help') 11 | .parse(process.argv) 12 | 13 | const commandTypeObject: {[key: string]: any} = { 14 | 'ls': { 15 | message: 'get list data action(获取列表数据)', 16 | childOptions: { 17 | '-c': 'list reactions for a commit comment', 18 | '-i': 'list reactions for an issue', 19 | '-ic': 'list reactions for an issue comment', 20 | '-p': 'list reactions for a pull request review comment' 21 | } 22 | }, 23 | 'cr': { 24 | message: 'create reaction action(创建数据)', 25 | childOptions: { 26 | '-c': 'create reaction for a commit comment', 27 | '-i': 'create reaction for an issue', 28 | '-ic': 'create reaction for an issue comment', 29 | '-p': 'create reaction for a pull request review comment' 30 | } 31 | } 32 | } 33 | 34 | program.on('--help', function () { 35 | mainTitle('Commands:') 36 | command() 37 | Object.keys(commandTypeObject).forEach((item: any) => { 38 | command(`$ gh rs ${item} --- ${commandTypeObject[item].message}`) 39 | let childOptions = commandTypeObject[item].childOptions 40 | if (childOptions) { 41 | describe('the supported child options for this command as follows:') 42 | Object.keys(childOptions).forEach((optionItem: any) => { 43 | command(` ${optionItem} --- ${childOptions[optionItem]}`) 44 | }) 45 | } 46 | }) 47 | mainTitle('use examples:') 48 | example() 49 | describe('list reactions for a commit comment:') 50 | example('$ gh rt ls -c') 51 | describe('list the repositories of another user:') 52 | example('$ gh rt ls -c -n') 53 | example() 54 | }) 55 | 56 | let paramArray = process.argv.slice(2) 57 | let thecmd = paramArray[0] // 命令类型 58 | let theoption = paramArray[1] // 参数值 59 | 60 | if (!thecmd || thecmd === '-h') { 61 | program.help() 62 | } 63 | 64 | if (!commandTypeObject.hasOwnProperty(thecmd)) { 65 | error('the command you input is invalid, you could get the surpported commands through $ gh rt -h') 66 | } 67 | let childOptions = commandTypeObject[thecmd].childOptions 68 | if (!theoption || !childOptions.hasOwnProperty(theoption)) { 69 | error('empty or invalid child option!') 70 | mainTitle('the supported child options as follows:') 71 | command() 72 | Object.keys(childOptions).forEach((item: any) => { 73 | command(`${item} --- ${childOptions[item]}`) 74 | }) 75 | command() 76 | describe('list all the reactions of an issue') 77 | example('$ gh rt ls -i') 78 | describe('list all the reactions of an issue of a repository of other user') 79 | example('$ gh rt ls -i -n') 80 | process.exit() 81 | } 82 | reactionStrategy[thecmd][theoption]() -------------------------------------------------------------------------------- /src/bin/github_users.ts: -------------------------------------------------------------------------------- 1 | #! /usr/bin/env node 2 | 3 | import * as program from 'commander' 4 | 5 | import { mainTitle, command, example, describe, error } from '../lib/tools/output' 6 | import { userStrategy } from '../lib/action/users'; 7 | 8 | program 9 | .version(require('../package.json').version) 10 | .option('-h', 'get help') 11 | .parse(process.argv) 12 | 13 | const commandTypeObject: {[key: string]: any} = { 14 | 'ls': { 15 | message: 'get list data(获取列表数据操作)', 16 | childOptions: { 17 | '-m': 'list followers of a user', 18 | '-t': 'list the following of a user' 19 | } 20 | }, 21 | 'et': { 22 | message: 'edit user data(编辑用户数据)' 23 | }, 24 | 'fl': { 25 | message: 'add a following(follow某个用户)' 26 | }, 27 | 'rf': { 28 | message: 'unfollow users(unfollow某个用户)' 29 | } 30 | } 31 | 32 | program.on('--help', function () { 33 | mainTitle('Commands:') 34 | command() 35 | Object.keys(commandTypeObject).forEach((commandItem) => { 36 | command(`$ gh us ${commandItem} --- ${commandTypeObject[commandItem].message}`) 37 | let childOptions = commandTypeObject[commandItem].childOptions 38 | if (childOptions) { 39 | describe('the supported child options for this command as follows:') 40 | Object.keys(childOptions).forEach((item) => { 41 | command(` ${item} --- ${childOptions[item]}`) 42 | }) 43 | } 44 | }) 45 | 46 | mainTitle('use alert tips:') 47 | describe('The repository of all these command actions using is in your own github username space, so if you want to act at other username space, what you need to do is adding a -n option') 48 | 49 | mainTitle('use examples:') 50 | example() 51 | describe('list followers of yourself') 52 | example('$ gh us ls -m') 53 | describe('list followers of another user') 54 | example('$ gh us ls -m -n(username is optional)') 55 | example() 56 | }) 57 | 58 | let paramArray = process.argv.slice(2) 59 | let thecmd = paramArray[0] // 命令类型 60 | let theoption = paramArray[1] // 参数值 61 | 62 | if (!thecmd || thecmd === '-h') { 63 | program.help() 64 | } 65 | 66 | if (!commandTypeObject.hasOwnProperty(thecmd)) { 67 | error('the command you input is invalid, you could get the surpported commands through $ gh issues -h') 68 | process.exit() 69 | } 70 | 71 | let childOptions = commandTypeObject[thecmd].childOptions 72 | if (childOptions) { 73 | if (!theoption || !childOptions.hasOwnProperty(theoption)) { 74 | error('empty or invalid child option!') 75 | mainTitle('the supported child options as follows:') 76 | command() 77 | Object.keys(childOptions).forEach((item: any) => { 78 | command(`${item} --- ${childOptions[item]}`) 79 | }) 80 | command() 81 | describe('list followers of yourself') 82 | example('$ gh us ls -m') 83 | describe('list followers of another user') 84 | example('$ gh us ls -m -n(username is optional)') 85 | process.exit() 86 | } else { 87 | userStrategy[thecmd][theoption]() 88 | } 89 | } else { 90 | userStrategy[thecmd]() 91 | } 92 | 93 | 94 | -------------------------------------------------------------------------------- /src/bin/index.ts: -------------------------------------------------------------------------------- 1 | #! /usr/bin/env node 2 | 3 | import * as program from 'commander' 4 | import {spawn} from 'child_process' 5 | import * as path from 'path' 6 | import * as fs from 'fs' 7 | import { mainTitle, command, example, describe, error } from '../lib/tools/output'; 8 | import textFonts from '../lib/tools/cfonts'; 9 | import * as updateNotifier from 'update-notifier' 10 | 11 | const pkg = require('../package.json') 12 | updateNotifier({pkg}).notify() 13 | 14 | // 支持的命令类型 15 | const commandTypeObject: { [key: string]: any } = { 16 | 'iu': { 17 | message: 'issues action(issues操作)', 18 | fontstext: 'issues' 19 | }, 20 | 'pr': { 21 | message: 'pull request action(pull request操作)', 22 | fontstext: 'pullReq' 23 | }, 24 | 'rt': { 25 | message: 'emojis action(表情回应)', 26 | fontstext: 'reaction' 27 | }, 28 | 'rs': { 29 | message: 'repository action(仓库操作)', 30 | fontstext: 'repos' 31 | }, 32 | 'sr': { 33 | message: 'search action(搜索操作)', 34 | fontstext: 'search' 35 | }, 36 | 'us': { 37 | message: 'personal user action(个人用户操作)', 38 | fontstext: 'users' 39 | } 40 | } 41 | 42 | program 43 | .version(pkg.version) 44 | .option('-h', 'get help') 45 | .parse(process.argv) 46 | 47 | program.on('--help', function() { 48 | mainTitle('Commands:') 49 | command() 50 | Object.keys(commandTypeObject).forEach((item) => { 51 | command(`$ gh ${item} --- ${commandTypeObject[item].message}`) 52 | }) 53 | command() 54 | 55 | mainTitle('Examples:') 56 | example() 57 | Object.keys(commandTypeObject).forEach((item) => { 58 | describe(`# look help for ${item} action`) 59 | example(`$ gh ${item} -h`) 60 | }) 61 | }) 62 | 63 | let args = process.argv.slice(3) 64 | let thecommand = program.args[0] 65 | 66 | if (!thecommand || thecommand === '-h') { 67 | program.help() 68 | } 69 | 70 | if (!commandTypeObject.hasOwnProperty(thecommand)) { 71 | error('the command you input is invalid,you could look for surpported commands through $ gh') 72 | process.exit() 73 | } 74 | 75 | if (args.indexOf('-n') > 0) { 76 | // if -n option exist, it indicate that you want do actions at another github user namespace 77 | process.env.githubUserMode = 'target' 78 | } 79 | 80 | if (process.argv.indexOf('rm') > 0) { 81 | process.env.githubActionType = 'remove' 82 | } 83 | 84 | // 子命令标题 85 | textFonts(commandTypeObject[thecommand].fontstext) 86 | 87 | let bin = `gh_${thecommand}` 88 | let binFilePath = path.join(__dirname, bin) 89 | let exists = fs.existsSync 90 | 91 | if (exists(binFilePath)) { 92 | bin = binFilePath 93 | } else { 94 | bin = process.env.PATH.split(':').reduce(function (binary, p) { 95 | p = path.resolve(p, bin) 96 | return exists(p) && fs.statSync(p).isFile() ? p : binary 97 | }, bin) 98 | } 99 | let task = spawn(bin, args, { 100 | stdio: 'inherit' 101 | }) 102 | 103 | task.on('close', function (code) { 104 | process.exit(code) 105 | }) 106 | 107 | task.on('error', function (err) { 108 | console.log(err) 109 | }) 110 | -------------------------------------------------------------------------------- /bin/index.js: -------------------------------------------------------------------------------- 1 | #! /usr/bin/env node 2 | "use strict"; 3 | Object.defineProperty(exports, "__esModule", { value: true }); 4 | var program = require("commander"); 5 | var child_process_1 = require("child_process"); 6 | var path = require("path"); 7 | var fs = require("fs"); 8 | var output_1 = require("../lib/tools/output"); 9 | var cfonts_1 = require("../lib/tools/cfonts"); 10 | var updateNotifier = require("update-notifier"); 11 | var pkg = require('../package.json'); 12 | updateNotifier({ pkg: pkg }).notify(); 13 | // 支持的命令类型 14 | var commandTypeObject = { 15 | 'iu': { 16 | message: 'issues action(issues操作)', 17 | fontstext: 'issues' 18 | }, 19 | 'pr': { 20 | message: 'pull request action(pull request操作)', 21 | fontstext: 'pullReq' 22 | }, 23 | 'rt': { 24 | message: 'emojis action(表情回应)', 25 | fontstext: 'reaction' 26 | }, 27 | 'rs': { 28 | message: 'repository action(仓库操作)', 29 | fontstext: 'repos' 30 | }, 31 | 'sr': { 32 | message: 'search action(搜索操作)', 33 | fontstext: 'search' 34 | }, 35 | 'us': { 36 | message: 'personal user action(个人用户操作)', 37 | fontstext: 'users' 38 | } 39 | }; 40 | program 41 | .version(pkg.version) 42 | .option('-h', 'get help') 43 | .parse(process.argv); 44 | program.on('--help', function () { 45 | output_1.mainTitle('Commands:'); 46 | output_1.command(); 47 | Object.keys(commandTypeObject).forEach(function (item) { 48 | output_1.command("$ gh " + item + " --- " + commandTypeObject[item].message); 49 | }); 50 | output_1.command(); 51 | output_1.mainTitle('Examples:'); 52 | output_1.example(); 53 | Object.keys(commandTypeObject).forEach(function (item) { 54 | output_1.describe("# look help for " + item + " action"); 55 | output_1.example("$ gh " + item + " -h"); 56 | }); 57 | }); 58 | var args = process.argv.slice(3); 59 | var thecommand = program.args[0]; 60 | if (!thecommand || thecommand === '-h') { 61 | program.help(); 62 | } 63 | if (!commandTypeObject.hasOwnProperty(thecommand)) { 64 | output_1.error('the command you input is invalid,you could look for surpported commands through $ gh'); 65 | process.exit(); 66 | } 67 | if (args.indexOf('-n') > 0) { 68 | // if -n option exist, it indicate that you want do actions at another github user namespace 69 | process.env.githubUserMode = 'target'; 70 | } 71 | if (process.argv.indexOf('rm') > 0) { 72 | process.env.githubActionType = 'remove'; 73 | } 74 | // 子命令标题 75 | cfonts_1.default(commandTypeObject[thecommand].fontstext); 76 | var bin = "gh_" + thecommand; 77 | var binFilePath = path.join(__dirname, bin); 78 | var exists = fs.existsSync; 79 | if (exists(binFilePath)) { 80 | bin = binFilePath; 81 | } 82 | else { 83 | bin = process.env.PATH.split(':').reduce(function (binary, p) { 84 | p = path.resolve(p, bin); 85 | return exists(p) && fs.statSync(p).isFile() ? p : binary; 86 | }, bin); 87 | } 88 | var task = child_process_1.spawn(bin, args, { 89 | stdio: 'inherit' 90 | }); 91 | task.on('close', function (code) { 92 | process.exit(code); 93 | }); 94 | task.on('error', function (err) { 95 | console.log(err); 96 | }); 97 | -------------------------------------------------------------------------------- /bin/github_reaction.js: -------------------------------------------------------------------------------- 1 | #! /usr/bin/env node 2 | "use strict"; 3 | Object.defineProperty(exports, "__esModule", { value: true }); 4 | var program = require("commander"); 5 | var output_1 = require("../lib/tools/output"); 6 | var reaction_1 = require("../lib/action/reaction"); 7 | program 8 | .version(require('../package.json').version) 9 | .option('-h', 'get help') 10 | .parse(process.argv); 11 | var commandTypeObject = { 12 | 'ls': { 13 | message: 'get list data action(获取列表数据)', 14 | childOptions: { 15 | '-c': 'list reactions for a commit comment', 16 | '-i': 'list reactions for an issue', 17 | '-ic': 'list reactions for an issue comment', 18 | '-p': 'list reactions for a pull request review comment' 19 | } 20 | }, 21 | 'cr': { 22 | message: 'create reaction action(创建数据)', 23 | childOptions: { 24 | '-c': 'create reaction for a commit comment', 25 | '-i': 'create reaction for an issue', 26 | '-ic': 'create reaction for an issue comment', 27 | '-p': 'create reaction for a pull request review comment' 28 | } 29 | } 30 | }; 31 | program.on('--help', function () { 32 | output_1.mainTitle('Commands:'); 33 | output_1.command(); 34 | Object.keys(commandTypeObject).forEach(function (item) { 35 | output_1.command("$ gh rs " + item + " --- " + commandTypeObject[item].message); 36 | var childOptions = commandTypeObject[item].childOptions; 37 | if (childOptions) { 38 | output_1.describe('the supported child options for this command as follows:'); 39 | Object.keys(childOptions).forEach(function (optionItem) { 40 | output_1.command(" " + optionItem + " --- " + childOptions[optionItem]); 41 | }); 42 | } 43 | }); 44 | output_1.mainTitle('use examples:'); 45 | output_1.example(); 46 | output_1.describe('list reactions for a commit comment:'); 47 | output_1.example('$ gh rt ls -c'); 48 | output_1.describe('list the repositories of another user:'); 49 | output_1.example('$ gh rt ls -c -n'); 50 | output_1.example(); 51 | }); 52 | var paramArray = process.argv.slice(2); 53 | var thecmd = paramArray[0]; // 命令类型 54 | var theoption = paramArray[1]; // 参数值 55 | if (!thecmd || thecmd === '-h') { 56 | program.help(); 57 | } 58 | if (!commandTypeObject.hasOwnProperty(thecmd)) { 59 | output_1.error('the command you input is invalid, you could get the surpported commands through $ gh rt -h'); 60 | } 61 | var childOptions = commandTypeObject[thecmd].childOptions; 62 | if (!theoption || !childOptions.hasOwnProperty(theoption)) { 63 | output_1.error('empty or invalid child option!'); 64 | output_1.mainTitle('the supported child options as follows:'); 65 | output_1.command(); 66 | Object.keys(childOptions).forEach(function (item) { 67 | output_1.command(item + " --- " + childOptions[item]); 68 | }); 69 | output_1.command(); 70 | output_1.describe('list all the reactions of an issue'); 71 | output_1.example('$ gh rt ls -i'); 72 | output_1.describe('list all the reactions of an issue of a repository of other user'); 73 | output_1.example('$ gh rt ls -i -n'); 74 | process.exit(); 75 | } 76 | reaction_1.reactionStrategy[thecmd][theoption](); 77 | -------------------------------------------------------------------------------- /bin/github_users.js: -------------------------------------------------------------------------------- 1 | #! /usr/bin/env node 2 | "use strict"; 3 | Object.defineProperty(exports, "__esModule", { value: true }); 4 | var program = require("commander"); 5 | var output_1 = require("../lib/tools/output"); 6 | var users_1 = require("../lib/action/users"); 7 | program 8 | .version(require('../package.json').version) 9 | .option('-h', 'get help') 10 | .parse(process.argv); 11 | var commandTypeObject = { 12 | 'ls': { 13 | message: 'get list data(获取列表数据操作)', 14 | childOptions: { 15 | '-m': 'list followers of a user', 16 | '-t': 'list the following of a user' 17 | } 18 | }, 19 | 'et': { 20 | message: 'edit user data(编辑用户数据)' 21 | }, 22 | 'fl': { 23 | message: 'add a following(follow某个用户)' 24 | }, 25 | 'rf': { 26 | message: 'unfollow users(unfollow某个用户)' 27 | } 28 | }; 29 | program.on('--help', function () { 30 | output_1.mainTitle('Commands:'); 31 | output_1.command(); 32 | Object.keys(commandTypeObject).forEach(function (commandItem) { 33 | output_1.command("$ gh us " + commandItem + " --- " + commandTypeObject[commandItem].message); 34 | var childOptions = commandTypeObject[commandItem].childOptions; 35 | if (childOptions) { 36 | output_1.describe('the supported child options for this command as follows:'); 37 | Object.keys(childOptions).forEach(function (item) { 38 | output_1.command(" " + item + " --- " + childOptions[item]); 39 | }); 40 | } 41 | }); 42 | output_1.mainTitle('use alert tips:'); 43 | output_1.describe('The repository of all these command actions using is in your own github username space, so if you want to act at other username space, what you need to do is adding a -n option'); 44 | output_1.mainTitle('use examples:'); 45 | output_1.example(); 46 | output_1.describe('list followers of yourself'); 47 | output_1.example('$ gh us ls -m'); 48 | output_1.describe('list followers of another user'); 49 | output_1.example('$ gh us ls -m -n(username is optional)'); 50 | output_1.example(); 51 | }); 52 | var paramArray = process.argv.slice(2); 53 | var thecmd = paramArray[0]; // 命令类型 54 | var theoption = paramArray[1]; // 参数值 55 | if (!thecmd || thecmd === '-h') { 56 | program.help(); 57 | } 58 | if (!commandTypeObject.hasOwnProperty(thecmd)) { 59 | output_1.error('the command you input is invalid, you could get the surpported commands through $ gh issues -h'); 60 | process.exit(); 61 | } 62 | var childOptions = commandTypeObject[thecmd].childOptions; 63 | if (childOptions) { 64 | if (!theoption || !childOptions.hasOwnProperty(theoption)) { 65 | output_1.error('empty or invalid child option!'); 66 | output_1.mainTitle('the supported child options as follows:'); 67 | output_1.command(); 68 | Object.keys(childOptions).forEach(function (item) { 69 | output_1.command(item + " --- " + childOptions[item]); 70 | }); 71 | output_1.command(); 72 | output_1.describe('list followers of yourself'); 73 | output_1.example('$ gh us ls -m'); 74 | output_1.describe('list followers of another user'); 75 | output_1.example('$ gh us ls -m -n(username is optional)'); 76 | process.exit(); 77 | } 78 | else { 79 | users_1.userStrategy[thecmd][theoption](); 80 | } 81 | } 82 | else { 83 | users_1.userStrategy[thecmd](); 84 | } 85 | -------------------------------------------------------------------------------- /src/bin/github_issues.ts: -------------------------------------------------------------------------------- 1 | #! /usr/bin/env node 2 | 3 | import * as program from 'commander' 4 | 5 | import { mainTitle, command, example, describe, error } from '../lib/tools/output'; 6 | import { issueStrategies } from '../lib/action/issues' 7 | 8 | program 9 | .version(require('../package.json').version) 10 | .option('-h', 'get help') 11 | .parse(process.argv) 12 | 13 | const commandTypeObject: {[key: string]: any} = { 14 | 'ls': { 15 | message: 'get issues list data actions(获取列表数据操作)', 16 | childOptions: { 17 | '-u': 'list issues for myself', 18 | '-r': 'list issues for repository', 19 | '-a': 'list all asignees of repository issues', 20 | '-c': 'list all comments of a issue', 21 | '-cr': 'list comments of a repository' 22 | } 23 | }, 24 | 'cr': { 25 | message: 'create actions(创建issues数据)', 26 | childOptions: { 27 | '-r': 'create an issue for a repository', 28 | '-a': 'create assignees for an issue', 29 | '-c': 'create a comment for an issue', 30 | '-l': 'create labels for an issue' 31 | } 32 | }, 33 | 'et': { 34 | message: 'edit issues actions(编辑issues数据)', 35 | childOptions: { 36 | '-i': 'edit an issue', 37 | '-c': 'edit a comment for an issue', 38 | '-r': 'replace labels for an issue' 39 | } 40 | }, 41 | 'rm': { 42 | message: 'delete issues actions(删除issues数据)', 43 | childOptions: { 44 | '-a': 'delete assignees for an issue', 45 | '-c': 'delete comments for issue', 46 | '-l': 'delete labels for an issue' 47 | } 48 | } 49 | } 50 | 51 | program.on('--help', function () { 52 | mainTitle('Commands:') 53 | command() 54 | Object.keys(commandTypeObject).forEach((commandItem) => { 55 | command(`$ gh iu ${commandItem} --- ${commandTypeObject[commandItem].message}`) 56 | describe('the supported child options for this command as follows:') 57 | let childOptions = commandTypeObject[commandItem].childOptions 58 | Object.keys(childOptions).forEach((item) => { 59 | command(` ${item} --- ${childOptions[item]}`) 60 | }) 61 | }) 62 | 63 | mainTitle('use alert tips:') 64 | describe('The repository of all these command actions using is in your own github username space, so if you want to act at other username space, what you need to do is adding a -n option') 65 | 66 | mainTitle('use examples:') 67 | example() 68 | describe('list all the issues of a repository') 69 | example('$ gh iu ls -r') 70 | describe('list all the issues of a repository of a new github user') 71 | example('$ gh iu ls -r -n username(username is optional)') 72 | example() 73 | }) 74 | 75 | let paramArray = process.argv.slice(2) 76 | let thecmd = paramArray[0] // 命令类型 77 | let theoption = paramArray[1] // 参数值 78 | 79 | if (!thecmd || thecmd === '-h') { 80 | program.help() 81 | } 82 | 83 | if (!commandTypeObject.hasOwnProperty(thecmd)) { 84 | error('the command you input is invalid, you could get the surpported commands through $ gh issues -h') 85 | process.exit() 86 | } 87 | 88 | let childOptions = commandTypeObject[thecmd].childOptions 89 | if (!theoption || !childOptions.hasOwnProperty(theoption)) { 90 | error('empty or invalid child option!') 91 | mainTitle('the supported child options as follows:') 92 | command() 93 | Object.keys(childOptions).forEach((item: any) => { 94 | command(`${item} --- ${childOptions[item]}`) 95 | }) 96 | command() 97 | describe('list all the issues of a repository') 98 | example('$ gh iu ls -r') 99 | process.exit() 100 | } 101 | 102 | // execute the function corresponding to the command and the childoption 103 | issueStrategies[thecmd][theoption]() 104 | -------------------------------------------------------------------------------- /src/bin/github_pullrequest.ts: -------------------------------------------------------------------------------- 1 | #! /usr/bin/env node 2 | 3 | import * as program from 'commander' 4 | 5 | import { mainTitle, command, example, describe, error } from '../lib/tools/output' 6 | import { prStrategies } from '../lib/action/pullrequest' 7 | 8 | const commandTypeObject: {[key: string]: any} = { 9 | 'ls': { 10 | message: 'get list data about pull request(获取pull request列表数据操作)', 11 | childOptions: { 12 | '-r': 'list pull requests for a repository', 13 | '-v': 'list all the reviews of a pull request', 14 | '-c': 'list comments on a pull request', 15 | '-cw': 'list comments of a review of a pull request', 16 | '-cr': 'list comments in a repository pulls', 17 | '-rp': 'list review requests of a pull request' 18 | } 19 | }, 20 | 'cr': { 21 | message: 'create actions about pull request(创建关于pull request数据的操作)', 22 | childOptions: { 23 | '-p': 'create a pull request', 24 | '-pr': 'create a pull request review', 25 | '-rp': 'create a review request for a pull request' 26 | } 27 | }, 28 | 'et': { 29 | message: 'edit pull request actions(编辑pull request数据)', 30 | childOptions: { 31 | '-p': 'edit a pull request', 32 | '-c': 'edit a comment for repository pulls' 33 | } 34 | }, 35 | 'rm': { 36 | message: 'delete pull request actions(删除pull request数据)', 37 | childOptions: { 38 | '-v': 'delete reviews of a pull request', 39 | '-c': 'delete a comment of repository pulls', 40 | '-r': 'delete review request of a pull request' 41 | } 42 | }, 43 | 'mr': { 44 | message: 'merge pull request actions(合并pull request请求)' 45 | }, 46 | 'st': { 47 | message: 'submit pull request review' 48 | }, 49 | 'ds': { 50 | message: 'dismiss a pull request review' 51 | } 52 | } 53 | 54 | program 55 | .version(require('../package.json').version) 56 | .option('-h', 'get help') 57 | .parse(process.argv) 58 | 59 | program.on('--help', function () { 60 | mainTitle('Commands:') 61 | command() 62 | Object.keys(commandTypeObject).forEach((item: any) => { 63 | command(`$ gh pr ${item} --- ${commandTypeObject[item].message}`) 64 | let childOptions = commandTypeObject[item].childOptions 65 | if (childOptions) { 66 | describe('the supported child options for this command as follows:') 67 | Object.keys(childOptions).forEach((optionItem: any) => { 68 | command(` ${optionItem} --- ${childOptions[optionItem]}`) 69 | }) 70 | } 71 | }) 72 | mainTitle('use examples:') 73 | example() 74 | describe('list pull requests for a repository') 75 | example('gh pr ls -r') 76 | example() 77 | }) 78 | 79 | let paramArray = process.argv.slice(2) 80 | let thecmd = paramArray[0] // 命令类型 81 | let theoption = paramArray[1] // 参数值 82 | 83 | if (!thecmd || thecmd === '-h') { 84 | program.help() 85 | } 86 | 87 | if (!commandTypeObject.hasOwnProperty(thecmd)) { 88 | error('the command you input is invalid, you could get the surpported commands through $ gh pr -h') 89 | } 90 | 91 | let commandObject = commandTypeObject[thecmd] 92 | let childOptions = commandObject.childOptions 93 | if (childOptions) { 94 | if (!theoption || !childOptions.hasOwnProperty(theoption)) { 95 | error('empty or invalid child option!') 96 | mainTitle('the supported child options as follows:') 97 | command() 98 | Object.keys(childOptions).forEach((item: any) => { 99 | command(`${item} --- ${childOptions[item]}`) 100 | }) 101 | command() 102 | describe('list all the pull request of a repository') 103 | example('$ gh pr ls -r') 104 | process.exit() 105 | } else { 106 | prStrategies[thecmd][theoption]() 107 | } 108 | } else { 109 | prStrategies[thecmd]() 110 | } -------------------------------------------------------------------------------- /bin/github_issues.js: -------------------------------------------------------------------------------- 1 | #! /usr/bin/env node 2 | "use strict"; 3 | Object.defineProperty(exports, "__esModule", { value: true }); 4 | var program = require("commander"); 5 | var output_1 = require("../lib/tools/output"); 6 | var issues_1 = require("../lib/action/issues"); 7 | program 8 | .version(require('../package.json').version) 9 | .option('-h', 'get help') 10 | .parse(process.argv); 11 | var commandTypeObject = { 12 | 'ls': { 13 | message: 'get issues list data actions(获取列表数据操作)', 14 | childOptions: { 15 | '-u': 'list issues for myself', 16 | '-r': 'list issues for repository', 17 | '-a': 'list all asignees of repository issues', 18 | '-c': 'list all comments of a issue', 19 | '-cr': 'list comments of a repository' 20 | } 21 | }, 22 | 'cr': { 23 | message: 'create actions(创建issues数据)', 24 | childOptions: { 25 | '-r': 'create an issue for a repository', 26 | '-a': 'create assignees for an issue', 27 | '-c': 'create a comment for an issue', 28 | '-l': 'create labels for an issue' 29 | } 30 | }, 31 | 'et': { 32 | message: 'edit issues actions(编辑issues数据)', 33 | childOptions: { 34 | '-i': 'edit an issue', 35 | '-c': 'edit a comment for an issue', 36 | '-r': 'replace labels for an issue' 37 | } 38 | }, 39 | 'rm': { 40 | message: 'delete issues actions(删除issues数据)', 41 | childOptions: { 42 | '-a': 'delete assignees for an issue', 43 | '-c': 'delete comments for issue', 44 | '-l': 'delete labels for an issue' 45 | } 46 | } 47 | }; 48 | program.on('--help', function () { 49 | output_1.mainTitle('Commands:'); 50 | output_1.command(); 51 | Object.keys(commandTypeObject).forEach(function (commandItem) { 52 | output_1.command("$ gh iu " + commandItem + " --- " + commandTypeObject[commandItem].message); 53 | output_1.describe('the supported child options for this command as follows:'); 54 | var childOptions = commandTypeObject[commandItem].childOptions; 55 | Object.keys(childOptions).forEach(function (item) { 56 | output_1.command(" " + item + " --- " + childOptions[item]); 57 | }); 58 | }); 59 | output_1.mainTitle('use alert tips:'); 60 | output_1.describe('The repository of all these command actions using is in your own github username space, so if you want to act at other username space, what you need to do is adding a -n option'); 61 | output_1.mainTitle('use examples:'); 62 | output_1.example(); 63 | output_1.describe('list all the issues of a repository'); 64 | output_1.example('$ gh iu ls -r'); 65 | output_1.describe('list all the issues of a repository of a new github user'); 66 | output_1.example('$ gh iu ls -r -n username(username is optional)'); 67 | output_1.example(); 68 | }); 69 | var paramArray = process.argv.slice(2); 70 | var thecmd = paramArray[0]; // 命令类型 71 | var theoption = paramArray[1]; // 参数值 72 | if (!thecmd || thecmd === '-h') { 73 | program.help(); 74 | } 75 | if (!commandTypeObject.hasOwnProperty(thecmd)) { 76 | output_1.error('the command you input is invalid, you could get the surpported commands through $ gh issues -h'); 77 | process.exit(); 78 | } 79 | var childOptions = commandTypeObject[thecmd].childOptions; 80 | if (!theoption || !childOptions.hasOwnProperty(theoption)) { 81 | output_1.error('empty or invalid child option!'); 82 | output_1.mainTitle('the supported child options as follows:'); 83 | output_1.command(); 84 | Object.keys(childOptions).forEach(function (item) { 85 | output_1.command(item + " --- " + childOptions[item]); 86 | }); 87 | output_1.command(); 88 | output_1.describe('list all the issues of a repository'); 89 | output_1.example('$ gh iu ls -r'); 90 | process.exit(); 91 | } 92 | // execute the function corresponding to the command and the childoption 93 | issues_1.issueStrategies[thecmd][theoption](); 94 | -------------------------------------------------------------------------------- /description.md: -------------------------------------------------------------------------------- 1 | # 玩转Github的新姿势-github-cli 2 | 3 | ## 惊鸿一瞥 4 | 这是一个可以帮助你在命令行中完成Github的各种操作的cli工具。 5 | 创建它的初衷是因为我是一个命令行狂热爱好者,与此同时我也是一个Github的fans,每当我在命令行环境中进行开发工作时,如果此时我想看看Github上又诞生了什么新的有趣的开源项目或者我需要完成一些项目仓库之类的操作,我都需要切换到浏览器环境然后在Google中通过搜索跳转到Github的网站,找到我想要的页面去完成我要做的事。一顿操作猛如虎之后我又需要重新切回命令行环境,这样的环境切换既耗时又无趣。我又是一个很懒的人,自然这部分的时间浪费我也不能容忍。于是我开始搜索是否存在这样一个工具可以满足我的需求,但是搜索结果令人沮丧。既然如此,那就自己创造一个吧,谁让我们程序员是最有创造力的呢。 6 | ## 揭开面纱 7 | 前面见了Github-cli的匆匆一面,接下来就得看看它的庐山真面目了,嗯,需要聊一聊它该怎么用。首先你需要安装它 8 | ``` 9 | $ npm i -g @mozheng-neal/github_cli 10 | ``` 11 | 安装之后你可以通过 12 | ``` 13 | $ gh -h 14 | ``` 15 | 查看支持的命令和option。 16 | 在使用过程中需要注意的一点是有很多操作是需要做身份的权限校验的,github-cli通过Oauth的access token来完成校验。所以如果你遇到需要如下这样的要求你输入token的interface 17 | ![](https://s1.ax1x.com/2018/02/28/9B7vxP.jpg)你需要在[开发者设置](https://github.com/settings/tokens)生成并填写该token值,该token的生成方法如下所示: 18 | ![](https://ws2.sinaimg.cn/large/006tNc79gy1foqjrqrd5zj30rp09idhm.jpg) 19 | 20 | ![](https://ws3.sinaimg.cn/large/006tNc79gy1foqklllx1dj30r405n3zh.jpg) 21 | 22 | ![](https://ws4.sinaimg.cn/large/006tNc79gy1foqkn4p1utj30r608ita3.jpg) 23 | 24 | ![](https://s1.ax1x.com/2018/02/28/9B7pN9.jpg) 25 | 选择scopes时最好是选中所有的scope以防后续操作中遇到权限校验不通过的问题。token生成之后输入到对话框中即可,Github-cli会将您的token信息保存到本地,一次输入,长久有效。 26 | 目前该项目支持6个子域下的相关操作,包括repository,issues,pull request,reaction,search,users,接下来我们就逐一介绍 27 | ### Repository 28 | 你可以通过如下命令来查看该scope下支持的命令 29 | ``` 30 | $ gh rs -h 31 | ``` 32 | ![](https://ws3.sinaimg.cn/large/006tNc79gy1forfbbni9cj30vo0n4q54.jpg)  33 | 其中带有子option的命令类型拆解如下: 34 | 1. ls 35 | ![](https://ws1.sinaimg.cn/large/006tNc79gy1forjbol99pj30v80y4n0l.jpg) 36 | 37 | 2. cr 38 | ![](https://ws2.sinaimg.cn/large/006tNc79gy1forjz33usgj30v80gsab3.jpg) 39 | 40 | 3. et 41 | ![](https://ws4.sinaimg.cn/large/006tNc79gy1fork5o0yw4j30uw0bqjs0.jpg) 42 | 43 | 4. rm 44 | ![](https://ws4.sinaimg.cn/large/006tKfTcgy1forkfueuogj30qe0e00te.jpg) 45 | 46 | 5. st 47 | ![](https://ws4.sinaimg.cn/large/006tKfTcgy1forkk45hizj30r20e0q3p.jpg) 48 | 49 | 6. ck 50 | ![](https://ws1.sinaimg.cn/large/006tKfTcgy1forlbjkm2vj30v80c8t9c.jpg) 51 | 52 | ### Issues 53 | 你可以通过如下命令查看issues scope下支持的命令和option 54 | ``` 55 | $ gh iu -h 56 | ``` 57 | ![](https://ws1.sinaimg.cn/large/006tKfTcgy1forn3djo21j30pi0e0wf6.jpg) 58 | 其中带有子option的命令类型拆解如下: 59 | 1. ls 60 | ![](https://ws3.sinaimg.cn/large/006tKfTcgy1fornmrwnpmj30v80j2gmz.jpg) 61 | 62 | 2. cr 63 | ![](https://ws1.sinaimg.cn/large/006tKfTcgy1fornxpbdwmj30v80j2q47.jpg) 64 | 65 | 3. et 66 | ![](https://ws3.sinaimg.cn/large/006tKfTcgy1foro2hs895j30tw0bq74t.jpg) 67 | 68 | 4. rm 69 | ![](https://ws3.sinaimg.cn/large/006tKfTcgy1foro96dsnrj30ue0bqgm6.jpg) 70 | 71 | ### Pull request 72 | 通过如下指令你可以查看该scope下支持的命令 73 | ``` 74 | $ gh pr -h 75 | ``` 76 | ![](https://ws3.sinaimg.cn/large/006tKfTcgy1forp5e1vjtj30vo0kujtc.jpg) 77 | 其中带有子option的命令类型拆解如下: 78 | 79 | 1. ls 80 | ![](https://ws2.sinaimg.cn/large/006tKfTcgy1forpl9iqfbj30v80t6q5e.jpg) 81 | 82 | 2. cr 83 | ![](https://ws2.sinaimg.cn/large/006tKfTcgy1forq6ylblbj30v80d4js3.jpg) 84 | 85 | 3. et 86 | ![](https://ws3.sinaimg.cn/large/006tKfTcgy1forqavoqibj30v80au3yw.jpg) 87 | 88 | 4. rm 89 | ![](https://s1.ax1x.com/2018/02/27/9BPPYV.png) 90 | 91 | ### Reaction 92 | Reaction scope主要用来查看和创建一些对comment或者issue之类数据的表情回应,你可以通过如下命令查看它所支持的子命令 93 | ``` 94 | $ gh rt -h 95 | ``` 96 | ![](https://s1.ax1x.com/2018/02/27/9BFrZj.png) 97 | 其中带有子option的命令类型拆解如下: 98 | 1. ls 99 | ![](https://s1.ax1x.com/2018/02/27/9BeqfA.png) 100 | 101 | 2. cr 102 | ![](https://s1.ax1x.com/2018/02/27/9BnSjx.png) 103 | 104 | ### Search 105 | Search scope用于搜索操作,它支持的命令可以通过如下命令查看 106 | ``` 107 | $ gh sr -h 108 | ``` 109 | ![](https://s1.ax1x.com/2018/03/01/9rlbEq.png) 110 | ### Users 111 | 该scope主要用于查看和编辑一些个人数据,它所支持的子命令可以通过如下命令查看 112 | ``` 113 | $ gh us -h 114 | ``` 115 | 其使用细节如下图所示: 116 | ![](https://s1.ax1x.com/2018/02/27/9BMY2d.png) 117 | 118 | 119 | ## 结语 120 | 创建Github-cli的初衷只是为了节省我环境切换的时间以及享受自己亲手去设计和实现这样一个工具的快感,为了让它变得更好,并让那些像我一样遇到这种效率提升问题的开发小伙伴节省时间我决定将其开源出来,接受大家的意见反馈甚至参与共建。最后,欢迎大家使用这款工具,在使用过程中遇到任何问题可以直接提起issue,我会在第一时间给予回复。如果大家觉得这款工具不错,也请不要吝惜您的star。让我们一起把世界变得更美好。 -------------------------------------------------------------------------------- /bin/github_pullrequest.js: -------------------------------------------------------------------------------- 1 | #! /usr/bin/env node 2 | "use strict"; 3 | Object.defineProperty(exports, "__esModule", { value: true }); 4 | var program = require("commander"); 5 | var output_1 = require("../lib/tools/output"); 6 | var pullrequest_1 = require("../lib/action/pullrequest"); 7 | var commandTypeObject = { 8 | 'ls': { 9 | message: 'get list data about pull request(获取pull request列表数据操作)', 10 | childOptions: { 11 | '-r': 'list pull requests for a repository', 12 | '-v': 'list all the reviews of a pull request', 13 | '-c': 'list comments on a pull request', 14 | '-cw': 'list comments of a review of a pull request', 15 | '-cr': 'list comments in a repository pulls', 16 | '-rp': 'list review requests of a pull request' 17 | } 18 | }, 19 | 'cr': { 20 | message: 'create actions about pull request(创建关于pull request数据的操作)', 21 | childOptions: { 22 | '-p': 'create a pull request', 23 | '-pr': 'create a pull request review', 24 | '-rp': 'create a review request for a pull request' 25 | } 26 | }, 27 | 'et': { 28 | message: 'edit pull request actions(编辑pull request数据)', 29 | childOptions: { 30 | '-p': 'edit a pull request', 31 | '-c': 'edit a comment for repository pulls' 32 | } 33 | }, 34 | 'rm': { 35 | message: 'delete pull request actions(删除pull request数据)', 36 | childOptions: { 37 | '-v': 'delete reviews of a pull request', 38 | '-c': 'delete a comment of repository pulls', 39 | '-r': 'delete review request of a pull request' 40 | } 41 | }, 42 | 'mr': { 43 | message: 'merge pull request actions(合并pull request请求)' 44 | }, 45 | 'st': { 46 | message: 'submit pull request review' 47 | }, 48 | 'ds': { 49 | message: 'dismiss a pull request review' 50 | } 51 | }; 52 | program 53 | .version(require('../package.json').version) 54 | .option('-h', 'get help') 55 | .parse(process.argv); 56 | program.on('--help', function () { 57 | output_1.mainTitle('Commands:'); 58 | output_1.command(); 59 | Object.keys(commandTypeObject).forEach(function (item) { 60 | output_1.command("$ gh pr " + item + " --- " + commandTypeObject[item].message); 61 | var childOptions = commandTypeObject[item].childOptions; 62 | if (childOptions) { 63 | output_1.describe('the supported child options for this command as follows:'); 64 | Object.keys(childOptions).forEach(function (optionItem) { 65 | output_1.command(" " + optionItem + " --- " + childOptions[optionItem]); 66 | }); 67 | } 68 | }); 69 | output_1.mainTitle('use examples:'); 70 | output_1.example(); 71 | output_1.describe('list pull requests for a repository'); 72 | output_1.example('gh pr ls -r'); 73 | output_1.example(); 74 | }); 75 | var paramArray = process.argv.slice(2); 76 | var thecmd = paramArray[0]; // 命令类型 77 | var theoption = paramArray[1]; // 参数值 78 | if (!thecmd || thecmd === '-h') { 79 | program.help(); 80 | } 81 | if (!commandTypeObject.hasOwnProperty(thecmd)) { 82 | output_1.error('the command you input is invalid, you could get the surpported commands through $ gh pr -h'); 83 | } 84 | var commandObject = commandTypeObject[thecmd]; 85 | var childOptions = commandObject.childOptions; 86 | if (childOptions) { 87 | if (!theoption || !childOptions.hasOwnProperty(theoption)) { 88 | output_1.error('empty or invalid child option!'); 89 | output_1.mainTitle('the supported child options as follows:'); 90 | output_1.command(); 91 | Object.keys(childOptions).forEach(function (item) { 92 | output_1.command(item + " --- " + childOptions[item]); 93 | }); 94 | output_1.command(); 95 | output_1.describe('list all the pull request of a repository'); 96 | output_1.example('$ gh pr ls -r'); 97 | process.exit(); 98 | } 99 | else { 100 | pullrequest_1.prStrategies[thecmd][theoption](); 101 | } 102 | } 103 | else { 104 | pullrequest_1.prStrategies[thecmd](); 105 | } 106 | -------------------------------------------------------------------------------- /src/bin/github_repos.ts: -------------------------------------------------------------------------------- 1 | #! /usr/bin/env node 2 | 3 | import * as program from 'commander' 4 | 5 | import { mainTitle, command, example, describe, error } from '../lib/tools/output' 6 | import { reposStrategies } from '../lib/action/repos'; 7 | 8 | program 9 | .version(require('../package.json').version) 10 | .option('-h', 'get help') 11 | .parse(process.argv) 12 | 13 | const commandTypeObject: {[key: string]: any} = { 14 | 'ls': { 15 | message: 'get repository list data actions(获取列表数据操作)', 16 | childOptions: { 17 | '-r': 'list repositories', 18 | '-b': 'list all branches of a repository', 19 | '-t': 'list all topics of a repositories', 20 | '-c': 'list all contributors of a repository', 21 | '-s': 'list all starred repositories', 22 | '-w': 'list all watching repositories', 23 | '-i': 'list commits of a repository', 24 | '-o': 'list all collaborators of a repository', 25 | '-m': 'list milestones of a repository', 26 | '-l': 'list all the labels of a repository', 27 | '-cm': 'list commit comments of a repository' 28 | } 29 | }, 30 | 'cr': { 31 | message: 'create actions(创建repository数据)', 32 | childOptions: { 33 | '-r': 'create new repositories', 34 | '-a': 'add collaborator for a repository', 35 | '-m': 'create a milestone for a repository', 36 | '-l': 'create labels for a repository' 37 | } 38 | }, 39 | 'et': { 40 | message: 'edit actions(编辑repository数据)', 41 | childOptions: { 42 | '-t': 'replace topics for a repository', 43 | '-m': 'edit a milestone for a repository', 44 | '-l': 'edit a label for a repository' 45 | } 46 | }, 47 | 'rm': { 48 | message: 'delete issues actions(删除repository数据)', 49 | childOptions: { 50 | '-r': 'delete repositories', 51 | '-m': 'delete milestones of a repository', 52 | '-c': 'delete collaborators of a repository', 53 | '-l': 'delete labels of a repository' 54 | } 55 | }, 56 | 'st': { 57 | message: 'set actions(设置repository数据)', 58 | childOptions: { 59 | '-s': 'set subscription for repositories', 60 | '-r': 'star repositories', 61 | '-rn': 'unstar repositories', 62 | '-sn': 'unwatch repositories' 63 | } 64 | }, 65 | 'ck': { 66 | message: 'check actions(查看数据)', 67 | childOptions: { 68 | '-c': 'check whether a user is a collaborator of a repository', 69 | '-p': 'check the permission level of a collaborator' 70 | } 71 | }, 72 | 'ts': { 73 | message: 'transfer repositories to another user' 74 | }, 75 | 'fk': { 76 | message: 'fork a repository' 77 | } 78 | } 79 | program.on('--help', function () { 80 | mainTitle('Commands:') 81 | command() 82 | Object.keys(commandTypeObject).forEach((item: any) => { 83 | command(`$ gh rs ${item} --- ${commandTypeObject[item].message}`) 84 | let childOptions = commandTypeObject[item].childOptions 85 | if (childOptions) { 86 | describe('the supported child options for this command as follows:') 87 | Object.keys(childOptions).forEach((optionItem: any) => { 88 | command(` ${optionItem} --- ${childOptions[optionItem]}`) 89 | }) 90 | } 91 | }) 92 | mainTitle('use examples:') 93 | example() 94 | describe('list the repositories of yourself:') 95 | example('$ gh rs ls -r') 96 | describe('list the repositories of another user:') 97 | example('$ gh rs ls -r -n') 98 | example() 99 | }) 100 | 101 | let paramArray = process.argv.slice(2) 102 | let thecmd = paramArray[0] // 命令类型 103 | let theoption = paramArray[1] // 参数值 104 | 105 | if (!thecmd || thecmd === '-h') { 106 | program.help() 107 | } 108 | 109 | if (!commandTypeObject.hasOwnProperty(thecmd)) { 110 | error('the command you input is invalid, you could get the supported command through $ gh rs -h') 111 | } 112 | 113 | let commandObject = commandTypeObject[thecmd] 114 | let childOptions = commandObject.childOptions 115 | if (childOptions) { 116 | if (!theoption || !childOptions.hasOwnProperty(theoption)) { 117 | error('empty or invalid child option!') 118 | mainTitle('the supported child options as follows:') 119 | command() 120 | Object.keys(childOptions).forEach((item: any) => { 121 | command(`${item} --- ${childOptions[item]}`) 122 | }) 123 | command() 124 | describe('list the repositories of yourself:') 125 | example('$ gh rs ls -r') 126 | describe('list the repositories of another user:') 127 | example('$ gh rs ls -r -n') 128 | example() 129 | process.exit() 130 | } else { 131 | reposStrategies[thecmd][theoption]() 132 | } 133 | } else { 134 | reposStrategies[thecmd]() 135 | } 136 | -------------------------------------------------------------------------------- /bin/github_repos.js: -------------------------------------------------------------------------------- 1 | #! /usr/bin/env node 2 | "use strict"; 3 | Object.defineProperty(exports, "__esModule", { value: true }); 4 | var program = require("commander"); 5 | var output_1 = require("../lib/tools/output"); 6 | var repos_1 = require("../lib/action/repos"); 7 | program 8 | .version(require('../package.json').version) 9 | .option('-h', 'get help') 10 | .parse(process.argv); 11 | var commandTypeObject = { 12 | 'ls': { 13 | message: 'get repository list data actions(获取列表数据操作)', 14 | childOptions: { 15 | '-r': 'list repositories', 16 | '-b': 'list all branches of a repository', 17 | '-t': 'list all topics of a repositories', 18 | '-c': 'list all contributors of a repository', 19 | '-s': 'list all starred repositories', 20 | '-w': 'list all watching repositories', 21 | '-i': 'list commits of a repository', 22 | '-o': 'list all collaborators of a repository', 23 | '-m': 'list milestones of a repository', 24 | '-l': 'list all the labels of a repository', 25 | '-cm': 'list commit comments of a repository' 26 | } 27 | }, 28 | 'cr': { 29 | message: 'create actions(创建repository数据)', 30 | childOptions: { 31 | '-r': 'create new repositories', 32 | '-a': 'add collaborator for a repository', 33 | '-m': 'create a milestone for a repository', 34 | '-l': 'create labels for a repository' 35 | } 36 | }, 37 | 'et': { 38 | message: 'edit actions(编辑repository数据)', 39 | childOptions: { 40 | '-t': 'replace topics for a repository', 41 | '-m': 'edit a milestone for a repository', 42 | '-l': 'edit a label for a repository' 43 | } 44 | }, 45 | 'rm': { 46 | message: 'delete issues actions(删除repository数据)', 47 | childOptions: { 48 | '-r': 'delete repositories', 49 | '-m': 'delete milestones of a repository', 50 | '-c': 'delete collaborators of a repository', 51 | '-l': 'delete labels of a repository' 52 | } 53 | }, 54 | 'st': { 55 | message: 'set actions(设置repository数据)', 56 | childOptions: { 57 | '-s': 'set subscription for repositories', 58 | '-r': 'star repositories', 59 | '-rn': 'unstar repositories', 60 | '-sn': 'unwatch repositories' 61 | } 62 | }, 63 | 'ck': { 64 | message: 'check actions(查看数据)', 65 | childOptions: { 66 | '-c': 'check whether a user is a collaborator of a repository', 67 | '-p': 'check the permission level of a collaborator' 68 | } 69 | }, 70 | 'ts': { 71 | message: 'transfer repositories to another user' 72 | }, 73 | 'fk': { 74 | message: 'fork a repository' 75 | } 76 | }; 77 | program.on('--help', function () { 78 | output_1.mainTitle('Commands:'); 79 | output_1.command(); 80 | Object.keys(commandTypeObject).forEach(function (item) { 81 | output_1.command("$ gh rs " + item + " --- " + commandTypeObject[item].message); 82 | var childOptions = commandTypeObject[item].childOptions; 83 | if (childOptions) { 84 | output_1.describe('the supported child options for this command as follows:'); 85 | Object.keys(childOptions).forEach(function (optionItem) { 86 | output_1.command(" " + optionItem + " --- " + childOptions[optionItem]); 87 | }); 88 | } 89 | }); 90 | output_1.mainTitle('use examples:'); 91 | output_1.example(); 92 | output_1.describe('list the repositories of yourself:'); 93 | output_1.example('$ gh rs ls -r'); 94 | output_1.describe('list the repositories of another user:'); 95 | output_1.example('$ gh rs ls -r -n'); 96 | output_1.example(); 97 | }); 98 | var paramArray = process.argv.slice(2); 99 | var thecmd = paramArray[0]; // 命令类型 100 | var theoption = paramArray[1]; // 参数值 101 | if (!thecmd || thecmd === '-h') { 102 | program.help(); 103 | } 104 | if (!commandTypeObject.hasOwnProperty(thecmd)) { 105 | output_1.error('the command you input is invalid, you could get the supported command through $ gh rs -h'); 106 | } 107 | var commandObject = commandTypeObject[thecmd]; 108 | var childOptions = commandObject.childOptions; 109 | if (childOptions) { 110 | if (!theoption || !childOptions.hasOwnProperty(theoption)) { 111 | output_1.error('empty or invalid child option!'); 112 | output_1.mainTitle('the supported child options as follows:'); 113 | output_1.command(); 114 | Object.keys(childOptions).forEach(function (item) { 115 | output_1.command(item + " --- " + childOptions[item]); 116 | }); 117 | output_1.command(); 118 | output_1.describe('list the repositories of yourself:'); 119 | output_1.example('$ gh rs ls -r'); 120 | output_1.describe('list the repositories of another user:'); 121 | output_1.example('$ gh rs ls -r -n'); 122 | output_1.example(); 123 | process.exit(); 124 | } 125 | else { 126 | repos_1.reposStrategies[thecmd][theoption](); 127 | } 128 | } 129 | else { 130 | repos_1.reposStrategies[thecmd](); 131 | } 132 | -------------------------------------------------------------------------------- /src/lib/tools/verification.ts: -------------------------------------------------------------------------------- 1 | import { request, thedomain } from './request'; 2 | import askquestion from './askQuestion'; 3 | import spinner from './spinner' 4 | import { saveInfo, getInfo } from './saveInfo'; 5 | import { info } from './output' 6 | import { reposActions } from '../action/repos'; 7 | 8 | // 获取想要操作的Github用户名 9 | const getTargetUserName = function (message: string, fn?: Function, isNeedAsk: boolean = true) { 10 | function verifyName (username: string) { 11 | spinner.start('verifing the username you input') 12 | request(`/users/${username}`, 'get', {}).then((res: any) => { 13 | spinner.succeed('username verify success') 14 | process.env.githubTargetUserName = username 15 | // add those used names to an array to record some names used frequently 16 | getInfo().then((res: any) => { 17 | let githubTargetUserNames = (Array.isArray(res.githubTargetUserNames) && res.githubTargetUserNames) || [] 18 | if (githubTargetUserNames.indexOf(username) < 0) { 19 | githubTargetUserNames.push(username) 20 | saveInfo({githubTargetUserNames: githubTargetUserNames}) 21 | } 22 | }) 23 | fn && fn(username) 24 | }).catch((err: any) => { 25 | getTargetUserName('the username you input is invalid,please input again:', fn) 26 | }) 27 | } 28 | // if user do not input a username 29 | if (isNeedAsk) { 30 | askquestion([{ 31 | type: 'input', 32 | name: 'username', 33 | message: message 34 | }], function (answers: any) { 35 | let theInputName = answers.username 36 | verifyName(theInputName) 37 | }) 38 | } else { 39 | // if user has inputed a username 40 | verifyName(process.env.githubTargetUserName) 41 | } 42 | } 43 | 44 | // 获取当前主用户的Github用户名 45 | const getSelfUserName = function (fn?: Function, isNeedGet: boolean = true) { 46 | function validateProcess (data: any) { 47 | if (!data.githubUserName) { 48 | getTargetUserName('please input your github username', function (validName: string) { 49 | process.env.githubUserName = validName 50 | saveInfo({githubUserName: validName}) 51 | fn && fn(validName) 52 | }) 53 | } else { 54 | process.env.githubUserName = data.githubUserName 55 | fn && fn(data.githubUserName) 56 | } 57 | } 58 | if (isNeedGet) { 59 | getInfo().then((res: any) => { 60 | validateProcess(res) 61 | }) 62 | } else { 63 | validateProcess(process.env) 64 | } 65 | } 66 | 67 | // choose githubTargetUserName or githubUserName 68 | export const getUserName = function (fn: Function, isNeedTarget: boolean = false) { 69 | if (isNeedTarget) { 70 | getInfo().then((res: any) => { 71 | let githubTargetUserNames = res.githubTargetUserNames 72 | if (githubTargetUserNames && githubTargetUserNames.length > 0) { 73 | askquestion([{ 74 | type: 'list', 75 | name: 'targetname', 76 | message: 'you may want to select one from these usernames:', 77 | choices: githubTargetUserNames.concat('no one matched') 78 | }], function (answers: any) { 79 | if (answers.targetname === 'no one matched') { 80 | getTargetUserName('please input the target gihub username:', fn) 81 | } else { 82 | fn(answers.targetname) 83 | } 84 | }) 85 | } else { 86 | getTargetUserName('please input the target gihub username:', fn) 87 | } 88 | }) 89 | } else { 90 | if (!process.env.githubUserName) { 91 | getInfo().then((res: any) => { 92 | if (!res.githubUserName) { 93 | getSelfUserName(fn, false) 94 | } else { 95 | process.env.githubUserName = res.githubUserName 96 | fn(res.githubUserName) 97 | } 98 | }) 99 | } else { 100 | fn(process.env.githubUserName) 101 | } 102 | } 103 | } 104 | 105 | // check whether the repository name that user input is invalid 106 | export const validateRepos = function (username: string, reposname: string, fn: Function) { 107 | function askRepos (message: string) { 108 | askquestion([{ 109 | type: 'input', 110 | name: 'reposname', 111 | message: message 112 | }], function (answers: any) { 113 | verifyRepo(answers.reposname) 114 | }) 115 | } 116 | function verifyRepo (reposname: string) { 117 | spinner.start('verifying the repos name you input') 118 | request(`/repos/${username}/${reposname}`, 'get', {}).then((res: any) => { 119 | spinner.succeed('repository verify success') 120 | fn(reposname) 121 | }).catch((err: any) => { 122 | // 如果仓库校验失败,则提示用户重新操作 123 | spinner.fail('repository verify error') 124 | askRepos('the repo name you input is invalid, please input again:') 125 | console.log(err) 126 | }) 127 | } 128 | } 129 | 130 | // get repositories of a github user to help user select 131 | export const selectRepos = function (fn: Function, isNeedTarget: boolean = false, type: string = 'checkbox') { 132 | function selectReposList (reposdataList: any, targetName?: string) { 133 | if (reposdataList.length > 0) { 134 | let thereposNameList = reposdataList.map((item: any) => { 135 | return item.name 136 | }) 137 | askquestion([{ 138 | type: type, 139 | name: 'reposlist', 140 | message: 'please select the repository you need:', 141 | choices: thereposNameList 142 | }], function (answers: any) { 143 | fn(answers.reposlist, targetName) 144 | }) 145 | } else { 146 | info('no repositories existed!please create it first') 147 | } 148 | } 149 | if (isNeedTarget) { 150 | getUserName(function (targetName: string) { 151 | reposActions.getReposForUser(targetName).then((reposdataList: any) => { 152 | selectReposList(reposdataList, targetName) 153 | }) 154 | }, true) 155 | } else { 156 | reposActions.getAll().then((reposdataList: any) => { 157 | selectReposList(reposdataList) 158 | }) 159 | } 160 | } 161 | 162 | // selectrepos 163 | export const selectReposWithMode = function (fn: Function, type: string = 'list') { 164 | if (process.env.githubUserMode === 'target') { 165 | selectRepos((reposname: string, targetName: string) => { 166 | fn(reposname, targetName) 167 | }, true, type) 168 | } else { 169 | getUserName((ownername: string) => { 170 | selectRepos((reposname: string) => { 171 | fn(reposname, ownername) 172 | }, false, type) 173 | }) 174 | } 175 | } 176 | 177 | // 获取个人的access-token 178 | export const getToken = function () { 179 | return (new Promise(function(resolve, reject) { 180 | if (process.env.githubToken) { 181 | resolve() 182 | } else { 183 | getInfo().then((res: any) => { 184 | if (!res.githubToken) { 185 | askquestion([{ 186 | type: 'input', 187 | name: 'accesstoken', 188 | message: 'please input your github access token:' 189 | }], function (answers: any) { 190 | let thetoken = answers.accesstoken 191 | process.env.githubToken = thetoken 192 | saveInfo({githubToken: thetoken}) 193 | resolve() 194 | }) 195 | } else { 196 | process.env.githubToken = res.githubToken 197 | resolve() 198 | } 199 | }) 200 | } 201 | })).catch((err: any) => { 202 | console.log(err) 203 | }) 204 | } 205 | -------------------------------------------------------------------------------- /src/lib/action/users.ts: -------------------------------------------------------------------------------- 1 | import {request} from '../tools/request' 2 | import { getToken, getUserName } from '../tools/verification'; 3 | import promiseCompose from '../tools/promiseCompose'; 4 | import askquestion, {createChoiceTable} from '../tools/askQuestion'; 5 | import createTable from '../tools/tableShow'; 6 | import { success, info } from '../tools/output'; 7 | 8 | export const userActions = { 9 | // update personal info about a user 10 | editUser (editOptions: any) { 11 | return promiseCompose([getToken, () => { 12 | return request('/user', 'patch', editOptions.data, { 13 | headers: { 14 | 'Authorization': `token ${process.env.githubToken}` 15 | } 16 | }).then((res: any) => { 17 | return res.data 18 | }) 19 | }]) 20 | }, 21 | // list followers of a user 22 | listFollowers (listOptions: any) { 23 | return request(`/users/${listOptions.username}/followers`, 'get', {}) 24 | .then((res: any) => { 25 | return res.data 26 | }) 27 | }, 28 | // list own followers 29 | listOwnFollowers () { 30 | return promiseCompose([getToken, () => { 31 | return request('/user/followers', 'get', {}, { 32 | headers: { 33 | 'Authorization': `token ${process.env.githubToken}` 34 | } 35 | }).then((res: any) => { 36 | return res.data 37 | }) 38 | }]) 39 | }, 40 | // list who a user is following 41 | listFollowing (listOptions: any) { 42 | return request(`/users/${listOptions.username}/following`, 'get', {}) 43 | .then((res: any) => { 44 | return res.data 45 | }) 46 | }, 47 | // list own following 48 | listOwnFollowing () { 49 | return promiseCompose([getToken, () => { 50 | return request('/user/following', 'get', {}, { 51 | headers: { 52 | 'Authorization': `token ${process.env.githubToken}` 53 | } 54 | }).then((res: any) => { 55 | return res.data 56 | }) 57 | }]) 58 | }, 59 | // follow a user 60 | addFollower (options: any) { 61 | return promiseCompose([getToken, () => { 62 | return request(`/user/following/${options.username}`, 'put', {}, { 63 | headers: { 64 | 'Authorization': `token ${process.env.githubToken}` 65 | } 66 | }).then((res: any) => { 67 | return res.data 68 | }) 69 | }]) 70 | }, 71 | // unfollow a user 72 | deleteFollower (options: any) { 73 | return promiseCompose([getToken, () => { 74 | return Promise.all(options.usernames.map((item: any) => { 75 | return request(`/user/following/${item}`, 'delete', {}, { 76 | headers: { 77 | 'Authorization': `token ${process.env.githubToken}` 78 | } 79 | }) 80 | })).then((res: any) => { 81 | return res 82 | }) 83 | }]) 84 | } 85 | } 86 | 87 | export const userStrategy: {[key: string]: any} = { 88 | 'ls': { 89 | '-m': function () { 90 | function dataShow (datalist: any) { 91 | if (datalist.length > 0) { 92 | let dataTable: any = createTable({ 93 | head: ['name', 'detailUrl(cmd+click)'], 94 | colWidths: [20, 60] 95 | }) 96 | datalist.forEach((item: any) => { 97 | dataTable.push([item.login, item.html_url]) 98 | }) 99 | console.log(dataTable.toString()) 100 | } else { 101 | info('no followers existed!') 102 | } 103 | } 104 | if (process.env.githubUserMode === 'target') { 105 | getUserName((ownername: string) => { 106 | userActions.listFollowers({username: ownername}).then(dataShow) 107 | }, true) 108 | } else { 109 | userActions.listOwnFollowers().then(dataShow) 110 | } 111 | }, 112 | '-t': function () { 113 | function dataShow (datalist: any) { 114 | if (datalist.length > 0) { 115 | let dataTable: any = createTable({ 116 | head: ['name', 'detailUrl(cmd+click)'], 117 | colWidths: [20, 60] 118 | }) 119 | datalist.forEach((item: any) => { 120 | dataTable.push([item.login, item.html_url]) 121 | }) 122 | console.log(dataTable.toString()) 123 | } else { 124 | info('no following existed!') 125 | } 126 | } 127 | if (process.env.githubUserMode === 'target') { 128 | getUserName((ownername: string) => { 129 | userActions.listFollowing({username: ownername}).then(dataShow) 130 | }, true) 131 | } else { 132 | userActions.listOwnFollowing().then(dataShow) 133 | } 134 | } 135 | }, 136 | 'et': function () { 137 | let questionObject: {[key: string]: any} = { 138 | name: { 139 | type: 'input', 140 | name: 'name', 141 | message: 'please input your new name:' 142 | }, 143 | email: { 144 | type: 'input', 145 | name: 'email', 146 | message: 'please input your new email address:' 147 | }, 148 | blog: { 149 | type: 'input', 150 | name: 'blog', 151 | message: 'please input your new blog url address:' 152 | }, 153 | company: { 154 | type: 'input', 155 | name: 'company', 156 | message: 'please input your new company name:' 157 | }, 158 | location: { 159 | type: 'input', 160 | name: 'location', 161 | message: 'please input your new location:' 162 | }, 163 | hireable: { 164 | type: 'confirm', 165 | name: 'hireable', 166 | message: 'are you hireable?:' 167 | } 168 | } 169 | askquestion([{ 170 | type: 'checkbox', 171 | name: 'filter', 172 | message: 'please select items you want to update:', 173 | choices: Object.keys(questionObject) 174 | }], (answers: any) => { 175 | let questionArray = answers.filter.map((item: any) => { 176 | return questionObject[item] 177 | }) 178 | askquestion(questionArray, (theanswers: any) => { 179 | userActions.editUser({ 180 | data: theanswers 181 | }).then(() => { 182 | success('update personal information success!') 183 | }) 184 | }) 185 | }) 186 | }, 187 | 'fl': function () { 188 | getUserName((username: string) => { 189 | userActions.addFollower({username: username}).then((res: any) => { 190 | success('add following success!') 191 | }) 192 | }, true) 193 | }, 194 | 'rf': function () { 195 | userActions.listOwnFollowing().then((resdata: any) => { 196 | if (resdata.length > 0) { 197 | let heads = [{ 198 | value: 'name', 199 | type: 'title' 200 | }, { 201 | value: 'detailUrl(cmd+click)', 202 | type: 'url' 203 | }] 204 | askquestion([{ 205 | type: 'checkbox', 206 | name: 'removers', 207 | message: 'please select some following users to remove:', 208 | choices: createChoiceTable(heads, resdata.map((item: any) => { 209 | return [item.login, item.html_url] 210 | })) 211 | }], (answers: any) => { 212 | userActions.deleteFollower({ 213 | usernames: answers.removers.map((item: any) => { 214 | return item.split('│')[1].trim() 215 | }) 216 | }) 217 | }) 218 | } else { 219 | info('no following users existed!') 220 | } 221 | }) 222 | } 223 | } 224 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # Description 2 | This project is designed to supply the conveniance of completing a variety github actions in commamd line. 3 | I am a command line fans, just inputting the command and everything is well done gives me a lot of enjoyment, especially in the process of developing applications. 4 | At the same time, I visit github website frequently to look for something interesting. This results to that I have to checkout my acting environment,either from command line to website or the contrary, which is time consuming. I have searched for whether there exist some good command line tools could help me solve this problem, but the result is frustrating. So I decided to create a command line on my own. 5 | 6 | # Usage 7 | ## Install 8 | To use this tool, firstly you need install it, there are two ways you could achive the install. 9 | ``` 10 | $ npm i -g @mozheng-neal/github_cli // npm install 11 | ``` 12 | After install you could get the detail command information through 13 | ``` 14 | $ gh 15 | ``` 16 | 17 | ## Attention 18 | There is a situation may raise your attention in the process of usage. In the essence, command line is a client type, when you input a command to create, update, or remove data, you need complete identity authentication firstly, in github, this authentication could be accomplished by the way of creating a personal access token, so when you encounter a interface that alert you to input your identity token, 19 | ![](https://s1.ax1x.com/2018/02/28/9B7vxP.jpg) 20 | what you need to do firstly is create it at [generate token address](https://github.com/settings/tokens) 21 | 22 | 23 | ![](https://ws2.sinaimg.cn/large/006tNc79gy1foqjrqrd5zj30rp09idhm.jpg) 24 | 25 | ![](https://ws3.sinaimg.cn/large/006tNc79gy1foqklllx1dj30r405n3zh.jpg) 26 | 27 | ![](https://ws4.sinaimg.cn/large/006tNc79gy1foqkn4p1utj30r608ita3.jpg) 28 | 29 | ![](https://s1.ax1x.com/2018/02/28/9B7pN9.jpg) 30 | 31 | when you select the scopes for this access token,a better choice is making all those scopes checked in case this access token can not comlete some actions that need authentication. 32 | After input the description and select the scopes,you could click the generate token button to create your personal access token, thus copy this token to the command line question interface. 33 | 34 | ## Detail 35 | Currently, this command line tool has supported the main types of github actions, such as search, repositories, pull request, pesonal user, reaction, issues.The usage detail of these commands scope as follows: 36 | 37 | ### Repository 38 | You could get the supported command list of repository scope through 39 | ``` 40 | $ gh rs 41 | ``` 42 | rs command has eight child commands and nearly every child command has some child options, the detail introduction as follows: 43 | ![](https://ws3.sinaimg.cn/large/006tNc79gy1forfbbni9cj30vo0n4q54.jpg) 44 | 45 | 1. ls 46 | the child command 'ls' is for listting all the repositories data belong to a github user, it has some child options to get different type of data: 47 | ![](https://ws1.sinaimg.cn/large/006tNc79gy1forjbol99pj30v80y4n0l.jpg) 48 | 49 | 2. cr 50 | Th child command cr is for making create actions, it has following child options: 51 | ![](https://ws2.sinaimg.cn/large/006tNc79gy1forjz33usgj30v80gsab3.jpg) 52 | 53 | 3. et 54 | The child command et is for making edit actions, it has following child options: 55 | ![](https://ws4.sinaimg.cn/large/006tNc79gy1fork5o0yw4j30uw0bqjs0.jpg) 56 | 57 | 4. rm 58 | This child command is for making delete actions, it has following child options: 59 | ![](https://ws4.sinaimg.cn/large/006tKfTcgy1forkfueuogj30qe0e00te.jpg) 60 | 61 | 5. st 62 | This child command is for making set actions, it has following child options: 63 | ![](https://ws4.sinaimg.cn/large/006tKfTcgy1forkk45hizj30r20e0q3p.jpg) 64 | 65 | 6. ck 66 | This child command is for making check actions,it has following child options: 67 | ![](https://ws1.sinaimg.cn/large/006tKfTcgy1forlbjkm2vj30v80c8t9c.jpg) 68 | 69 | 7. ts 70 | This child command is for transfering your repositories to another githug user,it has no child options 71 | 72 | 8. fk 73 | This child command is for forking a repository,it has no child options 74 | 75 | ### Issues 76 | you could get the supported command list of issues scope through 77 | ``` 78 | $ gh iu 79 | ``` 80 | iu command has four child commands to complete different tasks, the detail command list as follows: 81 | ![](https://ws1.sinaimg.cn/large/006tKfTcgy1forn3djo21j30pi0e0wf6.jpg) 82 | 83 | 1. ls 84 | This command is for listing issues data, it has some child options to do different things: 85 | ![](https://ws3.sinaimg.cn/large/006tKfTcgy1fornmrwnpmj30v80j2gmz.jpg) 86 | 87 | 2. cr 88 | This command is for making create actions, it has following child options 89 | ![](https://ws1.sinaimg.cn/large/006tKfTcgy1fornxpbdwmj30v80j2q47.jpg) 90 | 91 | 3. et 92 | This command is for making edit issue actions,it has following child options 93 | ![](https://ws3.sinaimg.cn/large/006tKfTcgy1foro2hs895j30tw0bq74t.jpg) 94 | 95 | 4. rm 96 | This command is for making delete issue actions, it has following child options: 97 | ![](https://ws3.sinaimg.cn/large/006tKfTcgy1foro96dsnrj30ue0bqgm6.jpg) 98 | 99 | ### Pull request 100 | The commands of this scope are designed to complete the tasks of pull requests, you could get the supported command list through 101 | ``` 102 | $ gh pr 103 | ``` 104 | this command has seven child commands, the detail list as follows: 105 | ![](https://ws3.sinaimg.cn/large/006tKfTcgy1forp5e1vjtj30vo0kujtc.jpg) 106 | 107 | 1. ls 108 | This child command is for listing pull request data, it has some child options to display different types of data 109 | ![](https://ws2.sinaimg.cn/large/006tKfTcgy1forpl9iqfbj30v80t6q5e.jpg) 110 | 111 | 2. cr 112 | This child command is for making create actions,it support following child options: 113 | ![](https://ws2.sinaimg.cn/large/006tKfTcgy1forq6ylblbj30v80d4js3.jpg) 114 | 115 | 3. et 116 | This child command is for making edit actions for pull request,it support following child options: 117 | ![](https://ws3.sinaimg.cn/large/006tKfTcgy1forqavoqibj30v80au3yw.jpg) 118 | 119 | 4. rm 120 | This child command is for making delete actions for pull request,it support following child options: 121 | ![](https://s1.ax1x.com/2018/02/27/9BPPYV.png) 122 | 123 | 5. other child commands 124 | Except the commands described above,there are other three child commands without child options,they are 'mr', 'st', 'ds',I have introduced them on the initial image 125 | 126 | ### Reaction 127 | Reaction scope is responsible for processing some tasks using emoji.You could get the supported command list through 128 | ``` 129 | $ gh rt 130 | ``` 131 | The detail usage as follows: 132 | ![](https://s1.ax1x.com/2018/02/27/9BFrZj.png) 133 | 134 | 1. ls 135 | This child command is for listing datas about reactions,the supported child options as follows: 136 | ![](https://s1.ax1x.com/2018/02/27/9BeqfA.png) 137 | 138 | 2. cr 139 | This child command is for creating reactions, the suppored child options as follows: 140 | ![](https://s1.ax1x.com/2018/02/27/9BnSjx.png) 141 | 142 | ### Search 143 | This scope is responsible for completing the task of searching,you could get supported child options list through 144 | ``` 145 | $ gh sr 146 | ``` 147 | The detail usage as follows: 148 | ![](https://s1.ax1x.com/2018/03/01/9rlbEq.png) 149 | 150 | ### Users 151 | This scope is responsible for personal actions, you could get the supported child commands and options through 152 | ``` 153 | $ gh us 154 | ``` 155 | the detail usage as follows: 156 | ![](https://s1.ax1x.com/2018/02/27/9BMY2d.png) 157 | 158 | 159 | # End 160 | Initially, creating github-cli is just for save the time of checking in and out between the environments of command line and website,after using it for a month, I decided to share this tool with you and I hope that this tool could dou you a favor.If you have any question or suggestion about this tool,please contact me or create an issue for this repository.May you an enjoyable life! 161 | 162 | 163 | 164 | 165 | 166 | 167 | -------------------------------------------------------------------------------- /src/lib/tools/request.ts: -------------------------------------------------------------------------------- 1 | // 客户端请求模块 2 | 3 | let axios = require('axios') 4 | import spinner from './spinner' 5 | import { error, success } from './output'; 6 | export const thedomain = 'https://api.github.com' 7 | export const previewAccept = 'application/vnd.github.mercy-preview+json' 8 | 9 | interface requestOptions { 10 | // `url` is the server URL that will be used for the request 11 | url?: string; 12 | 13 | // `method` is the request method to be used when making the request 14 | method?: string; // default 15 | 16 | // `baseURL` will be prepended to `url` unless `url` is absolute. 17 | // It can be convenient to set `baseURL` for an instance of axios to pass relative URLs 18 | // to methods of that instance. 19 | baseURL?: string; 20 | 21 | // `transformRequest` allows changes to the request data before it is sent to the server 22 | // This is only applicable for request methods 'PUT', 'POST', and 'PATCH' 23 | // The last function in the array must return a string or an instance of Buffer, ArrayBuffer, 24 | // FormData or Stream 25 | // You may modify the headers object. 26 | transformRequest?: Array; // example: [function (data, headers) { 27 | // Do whatever you want to transform the data 28 | // return data; 29 | // }], 30 | 31 | // `transformResponse` allows changes to the response data to be made before 32 | // it is passed to then/catch 33 | transformResponse?: Array;// example: [function (data) { 34 | // Do whatever you want to transform the data 35 | // return data; 36 | // }], 37 | 38 | // `headers` are custom headers to be sent 39 | headers?: any; // example: {'X-Requested-With': 'XMLHttpRequest'}, 40 | 41 | // `params` are the URL parameters to be sent with the request 42 | // Must be a plain object or a URLSearchParams object 43 | params?: any; // example: { ID: 12345 } 44 | 45 | // `paramsSerializer` is an optional function in charge of serializing `params` 46 | // (e.g. https://www.npmjs.com/package/qs, http://api.jquery.com/jquery.param/) 47 | paramsSerializer?: any; // example: function(params) { 48 | // return Qs.stringify(params, {arrayFormat: 'brackets'}) 49 | // }, 50 | 51 | // `data` is the data to be sent as the request body 52 | // Only applicable for request methods 'PUT', 'POST', and 'PATCH' 53 | // When no `transformRequest` is set, must be of one of the following types: 54 | // - string, plain object, ArrayBuffer, ArrayBufferView, URLSearchParams 55 | // - Browser only: FormData, File, Blob 56 | // - Node only: Stream, Buffer 57 | data?: any; // example: { 58 | // firstName: 'Fred' 59 | // }, 60 | 61 | // `timeout` specifies the number of milliseconds before the request times out. 62 | // If the request takes longer than `timeout`, the request will be aborted. 63 | timeout?: number; // 1000, 64 | 65 | // `withCredentials` indicates whether or not cross-site Access-Control requests 66 | // should be made using credentials 67 | withCredentials?: boolean, // default false 68 | 69 | // `adapter` allows custom handling of requests which makes testing easier. 70 | // Return a promise and supply a valid response (see lib/adapters/README.md). 71 | adapter?: any;// example function (config) { 72 | /* ... */ 73 | // }, 74 | 75 | // `auth` indicates that HTTP Basic auth should be used, and supplies credentials. 76 | // This will set an `Authorization` header, overwriting any existing 77 | // `Authorization` custom headers you have set using `headers`. 78 | auth?: any; // example: { username: 'janedoe',password: 's00pers3cret'}, 79 | 80 | // `responseType` indicates the type of data that the server will respond with 81 | // options are 'arraybuffer', 'blob', 'document', 'json', 'text', 'stream' 82 | responseType?: string; // default 'json' 83 | 84 | // `xsrfCookieName` is the name of the cookie to use as a value for xsrf token 85 | xsrfCookieName?: string; // default 'XSRF-TOKEN' 86 | 87 | // `xsrfHeaderName` is the name of the http header that carries the xsrf token value 88 | xsrfHeaderName?: 'string'; // 'X-XSRF-TOKEN' default 89 | 90 | // `onUploadProgress` allows handling of progress events for uploads 91 | onUploadProgress?: any; // function (progressEvent) { 92 | // Do whatever you want with the native progress event 93 | // }, 94 | 95 | // `onDownloadProgress` allows handling of progress events for downloads 96 | onDownloadProgress?: any; // function (progressEvent) { 97 | // Do whatever you want with the native progress event 98 | // }, 99 | 100 | // `maxContentLength` defines the max size of the http response content allowed 101 | maxContentLength?: number, 102 | 103 | // `validateStatus` defines whether to resolve or reject the promise for a given 104 | // HTTP response status code. If `validateStatus` returns `true` (or is set to `null` 105 | // or `undefined`), the promise will be resolved; otherwise, the promise will be 106 | // rejected. 107 | validateStatus?: any; // function (status) { 108 | // return status >= 200 && status < 300; default 109 | // }, 110 | 111 | // `maxRedirects` defines the maximum number of redirects to follow in node.js. 112 | // If set to 0, no redirects will be followed. 113 | maxRedirects?: number, // default 5 114 | 115 | // `httpAgent` and `httpsAgent` define a custom agent to be used when performing http 116 | // and https requests, respectively, in node.js. This allows options to be added like 117 | // `keepAlive` that are not enabled by default. 118 | httpAgent?: any;// new http.Agent({ keepAlive: true }), 119 | httpsAgent?: any;// new https.Agent({ keepAlive: true }), 120 | 121 | // 'proxy' defines the hostname and port of the proxy server 122 | // Use `false` to disable proxies, ignoring environment variables. 123 | // `auth` indicates that HTTP Basic auth should be used to connect to the proxy, and 124 | // supplies credentials. 125 | // This will set an `Proxy-Authorization` header, overwriting any existing 126 | // `Proxy-Authorization` custom headers you have set using `headers`. 127 | proxy?: any; /* example: { 128 | host: '127.0.0.1', 129 | port: 9000, 130 | auth: { 131 | username: 'mikeymike', 132 | password: 'rapunz3l' 133 | } 134 | },*/ 135 | 136 | // `cancelToken` specifies a cancel token that can be used to cancel the request 137 | // (see Cancellation section below for details) 138 | cancelToken?: any; // new CancelToken(function (cancel) {}) 139 | } 140 | 141 | // 客户端请求封装 142 | export const request = function (url: string, type: string, data: any, requestOptions?: requestOptions) { 143 | let ajaxdata = type === 'get' ? {params: data} : {data: data, withCredentials: true} 144 | let configOptions = Object.assign({ 145 | url: `${thedomain}${url}`, 146 | method: type, 147 | timeout: 5000, 148 | ...ajaxdata 149 | }, requestOptions || {}) 150 | // there are some problems with axios promise, so I wrapped a new promise 151 | return (new Promise(function (resolve, reject) { 152 | axios(configOptions).catch((err: any) => { 153 | reject(err) 154 | }).then((res: any) => { 155 | // 备注,star仓库等操作成功后也会返回204 156 | if (res.status === 204 && process.env.githubActionType === 'remove') { 157 | success('delete success!') 158 | } 159 | resolve(res) 160 | }) 161 | })).catch((err: any) => { 162 | if (err.response && err.response.status === 404 && process.argv.indexOf('ck') > 0) { 163 | error('this user is not a collaborator!') 164 | return 165 | } 166 | if (err.response && err.response.data) { 167 | error(err.response.statusText) 168 | error(err.response.data.message) 169 | /*if (err.response.status === 401) { 170 | error('you are unauthorized') 171 | } 172 | if (err.response.status === 403) { 173 | error('your authentication is forbidden') 174 | } 175 | if (err.response.status === 410) { 176 | error('current action is disabled or deprecated') 177 | } 178 | if (err.response.status === 422) { 179 | error('unprocessable request,maybe the data you input is invalid') 180 | } 181 | if (err.response.status === 405)*/ 182 | // 有些查看操作,checkcolloborators如果结果为否会返回404 183 | } 184 | if (err.message === 'timeout of 5000ms exceeded') { 185 | error('request timeout,please try again') 186 | } 187 | process.exit() 188 | }) 189 | } 190 | -------------------------------------------------------------------------------- /lib/tools/verification.js: -------------------------------------------------------------------------------- 1 | "use strict"; 2 | Object.defineProperty(exports, "__esModule", { value: true }); 3 | var request_1 = require("./request"); 4 | var askQuestion_1 = require("./askQuestion"); 5 | var spinner_1 = require("./spinner"); 6 | var saveInfo_1 = require("./saveInfo"); 7 | var output_1 = require("./output"); 8 | var repos_1 = require("../action/repos"); 9 | // 获取想要操作的Github用户名 10 | var getTargetUserName = function (message, fn, isNeedAsk) { 11 | if (isNeedAsk === void 0) { isNeedAsk = true; } 12 | function verifyName(username) { 13 | spinner_1.default.start('verifing the username you input'); 14 | request_1.request("/users/" + username, 'get', {}).then(function (res) { 15 | spinner_1.default.succeed('username verify success'); 16 | process.env.githubTargetUserName = username; 17 | // add those used names to an array to record some names used frequently 18 | saveInfo_1.getInfo().then(function (res) { 19 | var githubTargetUserNames = (Array.isArray(res.githubTargetUserNames) && res.githubTargetUserNames) || []; 20 | if (githubTargetUserNames.indexOf(username) < 0) { 21 | githubTargetUserNames.push(username); 22 | saveInfo_1.saveInfo({ githubTargetUserNames: githubTargetUserNames }); 23 | } 24 | }); 25 | fn && fn(username); 26 | }).catch(function (err) { 27 | getTargetUserName('the username you input is invalid,please input again:', fn); 28 | }); 29 | } 30 | // if user do not input a username 31 | if (isNeedAsk) { 32 | askQuestion_1.default([{ 33 | type: 'input', 34 | name: 'username', 35 | message: message 36 | }], function (answers) { 37 | var theInputName = answers.username; 38 | verifyName(theInputName); 39 | }); 40 | } 41 | else { 42 | // if user has inputed a username 43 | verifyName(process.env.githubTargetUserName); 44 | } 45 | }; 46 | // 获取当前主用户的Github用户名 47 | var getSelfUserName = function (fn, isNeedGet) { 48 | if (isNeedGet === void 0) { isNeedGet = true; } 49 | function validateProcess(data) { 50 | if (!data.githubUserName) { 51 | getTargetUserName('please input your github username', function (validName) { 52 | process.env.githubUserName = validName; 53 | saveInfo_1.saveInfo({ githubUserName: validName }); 54 | fn && fn(validName); 55 | }); 56 | } 57 | else { 58 | process.env.githubUserName = data.githubUserName; 59 | fn && fn(data.githubUserName); 60 | } 61 | } 62 | if (isNeedGet) { 63 | saveInfo_1.getInfo().then(function (res) { 64 | validateProcess(res); 65 | }); 66 | } 67 | else { 68 | validateProcess(process.env); 69 | } 70 | }; 71 | // choose githubTargetUserName or githubUserName 72 | exports.getUserName = function (fn, isNeedTarget) { 73 | if (isNeedTarget === void 0) { isNeedTarget = false; } 74 | if (isNeedTarget) { 75 | saveInfo_1.getInfo().then(function (res) { 76 | var githubTargetUserNames = res.githubTargetUserNames; 77 | if (githubTargetUserNames && githubTargetUserNames.length > 0) { 78 | askQuestion_1.default([{ 79 | type: 'list', 80 | name: 'targetname', 81 | message: 'you may want to select one from these usernames:', 82 | choices: githubTargetUserNames.concat('no one matched') 83 | }], function (answers) { 84 | if (answers.targetname === 'no one matched') { 85 | getTargetUserName('please input the target gihub username:', fn); 86 | } 87 | else { 88 | fn(answers.targetname); 89 | } 90 | }); 91 | } 92 | else { 93 | getTargetUserName('please input the target gihub username:', fn); 94 | } 95 | }); 96 | } 97 | else { 98 | if (!process.env.githubUserName) { 99 | saveInfo_1.getInfo().then(function (res) { 100 | if (!res.githubUserName) { 101 | getSelfUserName(fn, false); 102 | } 103 | else { 104 | process.env.githubUserName = res.githubUserName; 105 | fn(res.githubUserName); 106 | } 107 | }); 108 | } 109 | else { 110 | fn(process.env.githubUserName); 111 | } 112 | } 113 | }; 114 | // check whether the repository name that user input is invalid 115 | exports.validateRepos = function (username, reposname, fn) { 116 | function askRepos(message) { 117 | askQuestion_1.default([{ 118 | type: 'input', 119 | name: 'reposname', 120 | message: message 121 | }], function (answers) { 122 | verifyRepo(answers.reposname); 123 | }); 124 | } 125 | function verifyRepo(reposname) { 126 | spinner_1.default.start('verifying the repos name you input'); 127 | request_1.request("/repos/" + username + "/" + reposname, 'get', {}).then(function (res) { 128 | spinner_1.default.succeed('repository verify success'); 129 | fn(reposname); 130 | }).catch(function (err) { 131 | // 如果仓库校验失败,则提示用户重新操作 132 | spinner_1.default.fail('repository verify error'); 133 | askRepos('the repo name you input is invalid, please input again:'); 134 | console.log(err); 135 | }); 136 | } 137 | }; 138 | // get repositories of a github user to help user select 139 | exports.selectRepos = function (fn, isNeedTarget, type) { 140 | if (isNeedTarget === void 0) { isNeedTarget = false; } 141 | if (type === void 0) { type = 'checkbox'; } 142 | function selectReposList(reposdataList, targetName) { 143 | if (reposdataList.length > 0) { 144 | var thereposNameList = reposdataList.map(function (item) { 145 | return item.name; 146 | }); 147 | askQuestion_1.default([{ 148 | type: type, 149 | name: 'reposlist', 150 | message: 'please select the repository you need:', 151 | choices: thereposNameList 152 | }], function (answers) { 153 | fn(answers.reposlist, targetName); 154 | }); 155 | } 156 | else { 157 | output_1.info('no repositories existed!please create it first'); 158 | } 159 | } 160 | if (isNeedTarget) { 161 | exports.getUserName(function (targetName) { 162 | repos_1.reposActions.getReposForUser(targetName).then(function (reposdataList) { 163 | selectReposList(reposdataList, targetName); 164 | }); 165 | }, true); 166 | } 167 | else { 168 | repos_1.reposActions.getAll().then(function (reposdataList) { 169 | selectReposList(reposdataList); 170 | }); 171 | } 172 | }; 173 | // selectrepos 174 | exports.selectReposWithMode = function (fn, type) { 175 | if (type === void 0) { type = 'list'; } 176 | if (process.env.githubUserMode === 'target') { 177 | exports.selectRepos(function (reposname, targetName) { 178 | fn(reposname, targetName); 179 | }, true, type); 180 | } 181 | else { 182 | exports.getUserName(function (ownername) { 183 | exports.selectRepos(function (reposname) { 184 | fn(reposname, ownername); 185 | }, false, type); 186 | }); 187 | } 188 | }; 189 | // 获取个人的access-token 190 | exports.getToken = function () { 191 | return (new Promise(function (resolve, reject) { 192 | if (process.env.githubToken) { 193 | resolve(); 194 | } 195 | else { 196 | saveInfo_1.getInfo().then(function (res) { 197 | if (!res.githubToken) { 198 | askQuestion_1.default([{ 199 | type: 'input', 200 | name: 'accesstoken', 201 | message: 'please input your github access token:' 202 | }], function (answers) { 203 | var thetoken = answers.accesstoken; 204 | process.env.githubToken = thetoken; 205 | saveInfo_1.saveInfo({ githubToken: thetoken }); 206 | resolve(); 207 | }); 208 | } 209 | else { 210 | process.env.githubToken = res.githubToken; 211 | resolve(); 212 | } 213 | }); 214 | } 215 | })).catch(function (err) { 216 | console.log(err); 217 | }); 218 | }; 219 | -------------------------------------------------------------------------------- /lib/action/users.js: -------------------------------------------------------------------------------- 1 | "use strict"; 2 | Object.defineProperty(exports, "__esModule", { value: true }); 3 | var request_1 = require("../tools/request"); 4 | var verification_1 = require("../tools/verification"); 5 | var promiseCompose_1 = require("../tools/promiseCompose"); 6 | var askQuestion_1 = require("../tools/askQuestion"); 7 | var tableShow_1 = require("../tools/tableShow"); 8 | var output_1 = require("../tools/output"); 9 | exports.userActions = { 10 | // update personal info about a user 11 | editUser: function (editOptions) { 12 | return promiseCompose_1.default([verification_1.getToken, function () { 13 | return request_1.request('/user', 'patch', editOptions.data, { 14 | headers: { 15 | 'Authorization': "token " + process.env.githubToken 16 | } 17 | }).then(function (res) { 18 | return res.data; 19 | }); 20 | }]); 21 | }, 22 | // list followers of a user 23 | listFollowers: function (listOptions) { 24 | return request_1.request("/users/" + listOptions.username + "/followers", 'get', {}) 25 | .then(function (res) { 26 | return res.data; 27 | }); 28 | }, 29 | // list own followers 30 | listOwnFollowers: function () { 31 | return promiseCompose_1.default([verification_1.getToken, function () { 32 | return request_1.request('/user/followers', 'get', {}, { 33 | headers: { 34 | 'Authorization': "token " + process.env.githubToken 35 | } 36 | }).then(function (res) { 37 | return res.data; 38 | }); 39 | }]); 40 | }, 41 | // list who a user is following 42 | listFollowing: function (listOptions) { 43 | return request_1.request("/users/" + listOptions.username + "/following", 'get', {}) 44 | .then(function (res) { 45 | return res.data; 46 | }); 47 | }, 48 | // list own following 49 | listOwnFollowing: function () { 50 | return promiseCompose_1.default([verification_1.getToken, function () { 51 | return request_1.request('/user/following', 'get', {}, { 52 | headers: { 53 | 'Authorization': "token " + process.env.githubToken 54 | } 55 | }).then(function (res) { 56 | return res.data; 57 | }); 58 | }]); 59 | }, 60 | // follow a user 61 | addFollower: function (options) { 62 | return promiseCompose_1.default([verification_1.getToken, function () { 63 | return request_1.request("/user/following/" + options.username, 'put', {}, { 64 | headers: { 65 | 'Authorization': "token " + process.env.githubToken 66 | } 67 | }).then(function (res) { 68 | return res.data; 69 | }); 70 | }]); 71 | }, 72 | // unfollow a user 73 | deleteFollower: function (options) { 74 | return promiseCompose_1.default([verification_1.getToken, function () { 75 | return Promise.all(options.usernames.map(function (item) { 76 | return request_1.request("/user/following/" + item, 'delete', {}, { 77 | headers: { 78 | 'Authorization': "token " + process.env.githubToken 79 | } 80 | }); 81 | })).then(function (res) { 82 | return res; 83 | }); 84 | }]); 85 | } 86 | }; 87 | exports.userStrategy = { 88 | 'ls': { 89 | '-m': function () { 90 | function dataShow(datalist) { 91 | if (datalist.length > 0) { 92 | var dataTable_1 = tableShow_1.default({ 93 | head: ['name', 'detailUrl(cmd+click)'], 94 | colWidths: [20, 60] 95 | }); 96 | datalist.forEach(function (item) { 97 | dataTable_1.push([item.login, item.html_url]); 98 | }); 99 | console.log(dataTable_1.toString()); 100 | } 101 | else { 102 | output_1.info('no followers existed!'); 103 | } 104 | } 105 | if (process.env.githubUserMode === 'target') { 106 | verification_1.getUserName(function (ownername) { 107 | exports.userActions.listFollowers({ username: ownername }).then(dataShow); 108 | }, true); 109 | } 110 | else { 111 | exports.userActions.listOwnFollowers().then(dataShow); 112 | } 113 | }, 114 | '-t': function () { 115 | function dataShow(datalist) { 116 | if (datalist.length > 0) { 117 | var dataTable_2 = tableShow_1.default({ 118 | head: ['name', 'detailUrl(cmd+click)'], 119 | colWidths: [20, 60] 120 | }); 121 | datalist.forEach(function (item) { 122 | dataTable_2.push([item.login, item.html_url]); 123 | }); 124 | console.log(dataTable_2.toString()); 125 | } 126 | else { 127 | output_1.info('no following existed!'); 128 | } 129 | } 130 | if (process.env.githubUserMode === 'target') { 131 | verification_1.getUserName(function (ownername) { 132 | exports.userActions.listFollowing({ username: ownername }).then(dataShow); 133 | }, true); 134 | } 135 | else { 136 | exports.userActions.listOwnFollowing().then(dataShow); 137 | } 138 | } 139 | }, 140 | 'et': function () { 141 | var questionObject = { 142 | name: { 143 | type: 'input', 144 | name: 'name', 145 | message: 'please input your new name:' 146 | }, 147 | email: { 148 | type: 'input', 149 | name: 'email', 150 | message: 'please input your new email address:' 151 | }, 152 | blog: { 153 | type: 'input', 154 | name: 'blog', 155 | message: 'please input your new blog url address:' 156 | }, 157 | company: { 158 | type: 'input', 159 | name: 'company', 160 | message: 'please input your new company name:' 161 | }, 162 | location: { 163 | type: 'input', 164 | name: 'location', 165 | message: 'please input your new location:' 166 | }, 167 | hireable: { 168 | type: 'confirm', 169 | name: 'hireable', 170 | message: 'are you hireable?:' 171 | } 172 | }; 173 | askQuestion_1.default([{ 174 | type: 'checkbox', 175 | name: 'filter', 176 | message: 'please select items you want to update:', 177 | choices: Object.keys(questionObject) 178 | }], function (answers) { 179 | var questionArray = answers.filter.map(function (item) { 180 | return questionObject[item]; 181 | }); 182 | askQuestion_1.default(questionArray, function (theanswers) { 183 | exports.userActions.editUser({ 184 | data: theanswers 185 | }).then(function () { 186 | output_1.success('update personal information success!'); 187 | }); 188 | }); 189 | }); 190 | }, 191 | 'fl': function () { 192 | verification_1.getUserName(function (username) { 193 | exports.userActions.addFollower({ username: username }).then(function (res) { 194 | output_1.success('add following success!'); 195 | }); 196 | }, true); 197 | }, 198 | 'rf': function () { 199 | exports.userActions.listOwnFollowing().then(function (resdata) { 200 | if (resdata.length > 0) { 201 | var heads = [{ 202 | value: 'name', 203 | type: 'title' 204 | }, { 205 | value: 'detailUrl(cmd+click)', 206 | type: 'url' 207 | }]; 208 | askQuestion_1.default([{ 209 | type: 'checkbox', 210 | name: 'removers', 211 | message: 'please select some following users to remove:', 212 | choices: askQuestion_1.createChoiceTable(heads, resdata.map(function (item) { 213 | return [item.login, item.html_url]; 214 | })) 215 | }], function (answers) { 216 | exports.userActions.deleteFollower({ 217 | usernames: answers.removers.map(function (item) { 218 | return item.split('│')[1].trim(); 219 | }) 220 | }); 221 | }); 222 | } 223 | else { 224 | output_1.info('no following users existed!'); 225 | } 226 | }); 227 | } 228 | }; 229 | -------------------------------------------------------------------------------- /src/lib/action/search.ts: -------------------------------------------------------------------------------- 1 | import {request} from '../tools/request' 2 | import { getToken, getUserName } from '../tools/verification' 3 | import askquestion from '../tools/askQuestion'; 4 | import createTable from '../tools/tableShow'; 5 | import { info, success } from '../tools/output' 6 | const acceptType = 'application/vnd.github.v3.text-match+json' 7 | 8 | export const searchActions = { 9 | // search repositories 10 | searchRepos (options: any) { 11 | return request(`/search/repositories`, 'get', options.data, { 12 | headers: { 13 | 'Accept': acceptType 14 | } 15 | }).then((res: any) => { 16 | let dataItems = res.data.items 17 | if (dataItems && dataItems.length > 0) { 18 | let dataTable: any = createTable({ 19 | head: ['name', 'owner', 'description', 'detailUrl(cmd+click)'], 20 | colWidths: [10, 16, 40, 60], 21 | wordWrap: true 22 | }) 23 | dataItems.forEach((item: any) => { 24 | dataTable.push([item.name, item.owner.login, item.description || 'empty description', item.html_url]) 25 | }) 26 | console.log(dataTable.toString()) 27 | } else { 28 | info('there is no repository match your search condition') 29 | } 30 | }) 31 | }, 32 | // search commits 33 | searchCommits (options: any) { 34 | return request(`/search/commits`, 'get', options.data, { 35 | headers: { 36 | 'Accept': 'application/vnd.github.cloak-preview' 37 | } 38 | }).then((res: any) => { 39 | let dataItems = res.data.items 40 | if (dataItems && dataItems.length > 0) { 41 | let dataTable: any = createTable({ 42 | head: ['sha', 'committer', 'commit message', 'detailUrl(cmd+click)'], 43 | colWidths: [10, 10, 40, 60], 44 | wordWrap: true 45 | }) 46 | dataItems.forEach((item: any) => { 47 | dataTable.push([item.sha, item.commit.author.name, item.commit.message, item.html_url]) 48 | }) 49 | console.log(dataTable.toString()) 50 | } else { 51 | info('there is no commit match your search condition') 52 | } 53 | }) 54 | }, 55 | // search issues 56 | searchIssues (options: any) { 57 | return request('/search/issues', 'get', options.data, { 58 | headers: { 59 | 'Accept': acceptType 60 | } 61 | }).then((res: any) => { 62 | let dataItems = res.data.items 63 | if (dataItems && dataItems.length > 0) { 64 | let dataTable: any = createTable({ 65 | head: ['title', 'state', 'body', 'detailUrl(cmd+click)'], 66 | colWidths: [20, 10, 40, 60], 67 | wordWrap: true 68 | }) 69 | dataItems.forEach((item: any) => { 70 | dataTable.push([item.title, item.state, item.body || 'empty description', item.html_url]) 71 | }) 72 | console.log(dataTable.toString()) 73 | } else { 74 | info('there is no issue match your search condition') 75 | } 76 | }) 77 | }, 78 | // search users 79 | searchUsers (options: any) { 80 | return request('/search/users', 'get', options.data, { 81 | headers: { 82 | 'Accept': acceptType 83 | } 84 | }).then((res: any) => { 85 | let dataItems = res.data.items 86 | if (dataItems && dataItems.length > 0) { 87 | let dataTable: any = createTable({ 88 | head: ['name', 'detailUrl(cmd+click)'], 89 | colWidths: [20, 60] 90 | }) 91 | dataItems.forEach((item: any) => { 92 | dataTable.push([item.login, item.html_url]) 93 | }) 94 | console.log(dataTable.toString()) 95 | } else { 96 | info('there is no user match your search condition') 97 | } 98 | }) 99 | }, 100 | // search code 101 | searchCode (options: any) { 102 | return request('/search/code', 'get', options.data, { 103 | headers: { 104 | 'Accept': acceptType 105 | } 106 | }).then((res: any) => { 107 | let dataItems = res.data.items 108 | if (dataItems && dataItems.length > 0) { 109 | let dataTable: any = createTable({ 110 | head: ['path', 'repos name', 'repos description', 'detailUrl(cmd+click)'], 111 | colWidths: [20, 20, 40, 60], 112 | wordWrap: true 113 | }) 114 | dataItems.forEach((item: any) => { 115 | dataTable.push([item.path, item.repository.full_name, item.repository.description, item.html_url]) 116 | }) 117 | console.log(dataTable.toString()) 118 | } else { 119 | info('there is no result match your search condition') 120 | } 121 | }) 122 | } 123 | } 124 | 125 | function searchWithData (questionObject: any, fn: Function, keyword?: string) { 126 | askquestion([{ 127 | type: 'checkbox', 128 | name: 'filters', 129 | message: 'you could select some filter conditions to search:', 130 | choices: Object.keys(questionObject) 131 | }], (answers: any) => { 132 | let questionArray = answers.filters.map((item: any) => { 133 | return questionObject[item] 134 | }) 135 | askquestion(questionArray, (theanswers: any) => { 136 | let conditionStr = Object.keys(theanswers).map((item: any) => { 137 | return item === 'topic' ? theanswers.topic.split(' ').map((topicItem: string) => { 138 | return `topic:${topicItem}` 139 | }).join('+') : `${item}:${theanswers[item]}` 140 | }).join('+') 141 | fn({ 142 | data: { 143 | q: keyword ? `${keyword}+${conditionStr}` : conditionStr 144 | } 145 | }) 146 | }) 147 | }) 148 | } 149 | 150 | export const searchStrategy: {[key: string]: any} = { 151 | 'r': function () { 152 | let questionObject: {[key: string]: any} = { 153 | 'fork(filter whether forked repositories should be included)': { 154 | type: 'confirm', 155 | name: 'fork', 156 | message: 'please confirm whether forked repositories should be included:' 157 | }, 158 | 'forks(filter repositoties based on the number of forks)': { 159 | type: 'input', 160 | name: 'forks', 161 | message: 'please input the minimal number of forks to filter:' 162 | }, 163 | 'language(filter repositories based on the language they are written in)': { 164 | type: 'input', 165 | name: 'language', 166 | message: 'please input the languages of searched repositories:' 167 | }, 168 | 'user(limit search action to a specific user)': { 169 | type: 'input', 170 | name: 'user', 171 | message: 'please input the ownername of searched repositories:' 172 | }, 173 | 'size(Finds repositories that match a certain size)': { 174 | type: 'input', 175 | name: 'size', 176 | message: 'please input the minimal size of searched repositories(in kb):' 177 | }, 178 | 'stars(Searches repositories based on the number of stars)': { 179 | type: 'input', 180 | name: 'stars', 181 | message: 'please input the minimal number of stars of searched repositories:' 182 | }, 183 | 'topic(Filters repositories based on the specified topic)': { 184 | type: 'input', 185 | name: 'topic', 186 | message: 'input the topics of searched repositories(split with space):', 187 | } 188 | } 189 | searchWithData(questionObject, searchActions.searchRepos) 190 | }, 191 | 'c': function () { 192 | let questionObject: {[key: string]: any} = { 193 | 'author(Matches commits authored by a user)': { 194 | type: 'input', 195 | name: 'author-name', 196 | message: 'please input the commit author name:' 197 | }, 198 | 'committer(Matches commits committed by a user)': { 199 | type: 'input', 200 | name: 'committer-name', 201 | message: 'please input the committer name:' 202 | }, 203 | 'merge(Matched commits whether be merged)': { 204 | type: 'confirm', 205 | name: 'merge', 206 | message: 'does you need filter merged commits' 207 | }, 208 | 'hash(Matches commits by hash)': { 209 | type: 'input', 210 | name: 'hash', 211 | message: 'please input the hash value:' 212 | }, 213 | 'repo( Limits searches to a specific repository)': { 214 | type: 'input', 215 | name: 'repo', 216 | message: 'please input the repository name:' 217 | } 218 | } 219 | searchWithData(questionObject, searchActions.searchCommits) 220 | }, 221 | 'i': function () { 222 | let questionObject: {[key: string]: any} = { 223 | 'type(With this qualifier you can restrict the search to issues or pull request)': { 224 | type: 'list', 225 | name: 'type', 226 | message: 'please select a search type:', 227 | choices: ['issue', 'pr'] 228 | }, 229 | 'author(Finds issues or pull requests created by a certain user)': { 230 | type: 'input', 231 | name: 'author', 232 | message: 'please input the author name to search:' 233 | }, 234 | 'assignee(Finds issues or pull requests that are assigned to a certain user)': { 235 | type: 'input', 236 | name: 'assignee', 237 | message: 'please input the assignee name to search:' 238 | }, 239 | 'mentions(Finds issues or pull requests that mention a certain user)': { 240 | type: 'input', 241 | name: 'mentions', 242 | message: 'please input the username mentioned to search:' 243 | }, 244 | 'commenter(Finds issues or pull requests that a certain user commented on)': { 245 | type: 'input', 246 | name: 'commenter', 247 | message: 'please input a commenter name to search:' 248 | }, 249 | 'state(Filter issues or pull requests based on whether they are open or closed)': { 250 | type: 'list', 251 | name: 'state', 252 | message: 'please select a state to search:', 253 | choices: ['open', 'closed'] 254 | }, 255 | 'language(Searches for issues or pull requests within repositories that match a certain language)': { 256 | type: 'input', 257 | name: 'language', 258 | message: 'please input the language that match the repository to search for issues:' 259 | } 260 | } 261 | searchWithData(questionObject, searchActions.searchIssues) 262 | }, 263 | 'u': function () { 264 | let questionObject: {[key: string]: any} = { 265 | 'repos(Filters users based on the number of repositories they have)': { 266 | type: 'input', 267 | name: 'repos', 268 | message: 'please input the minimal number of repositories to search:' 269 | }, 270 | 'language(Search for users that have repositories that match a certain language)': { 271 | type: 'input', 272 | name: 'language', 273 | message: 'please input the language that match the repositories of the searched users:' 274 | }, 275 | 'followers(Filter users based on the number of followers they have)': { 276 | type: 'input', 277 | name: 'followers', 278 | message: 'please input the minimal number of followers to searched users have:' 279 | } 280 | } 281 | searchWithData(questionObject, searchActions.searchUsers) 282 | } 283 | } -------------------------------------------------------------------------------- /lib/action/search.js: -------------------------------------------------------------------------------- 1 | "use strict"; 2 | Object.defineProperty(exports, "__esModule", { value: true }); 3 | var request_1 = require("../tools/request"); 4 | var askQuestion_1 = require("../tools/askQuestion"); 5 | var tableShow_1 = require("../tools/tableShow"); 6 | var output_1 = require("../tools/output"); 7 | var acceptType = 'application/vnd.github.v3.text-match+json'; 8 | exports.searchActions = { 9 | // search repositories 10 | searchRepos: function (options) { 11 | return request_1.request("/search/repositories", 'get', options.data, { 12 | headers: { 13 | 'Accept': acceptType 14 | } 15 | }).then(function (res) { 16 | var dataItems = res.data.items; 17 | if (dataItems && dataItems.length > 0) { 18 | var dataTable_1 = tableShow_1.default({ 19 | head: ['name', 'owner', 'description', 'detailUrl(cmd+click)'], 20 | colWidths: [10, 16, 40, 60], 21 | wordWrap: true 22 | }); 23 | dataItems.forEach(function (item) { 24 | dataTable_1.push([item.name, item.owner.login, item.description || 'empty description', item.html_url]); 25 | }); 26 | console.log(dataTable_1.toString()); 27 | } 28 | else { 29 | output_1.info('there is no repository match your search condition'); 30 | } 31 | }); 32 | }, 33 | // search commits 34 | searchCommits: function (options) { 35 | return request_1.request("/search/commits", 'get', options.data, { 36 | headers: { 37 | 'Accept': 'application/vnd.github.cloak-preview' 38 | } 39 | }).then(function (res) { 40 | var dataItems = res.data.items; 41 | if (dataItems && dataItems.length > 0) { 42 | var dataTable_2 = tableShow_1.default({ 43 | head: ['sha', 'committer', 'commit message', 'detailUrl(cmd+click)'], 44 | colWidths: [10, 10, 40, 60], 45 | wordWrap: true 46 | }); 47 | dataItems.forEach(function (item) { 48 | dataTable_2.push([item.sha, item.commit.author.name, item.commit.message, item.html_url]); 49 | }); 50 | console.log(dataTable_2.toString()); 51 | } 52 | else { 53 | output_1.info('there is no commit match your search condition'); 54 | } 55 | }); 56 | }, 57 | // search issues 58 | searchIssues: function (options) { 59 | return request_1.request('/search/issues', 'get', options.data, { 60 | headers: { 61 | 'Accept': acceptType 62 | } 63 | }).then(function (res) { 64 | var dataItems = res.data.items; 65 | if (dataItems && dataItems.length > 0) { 66 | var dataTable_3 = tableShow_1.default({ 67 | head: ['title', 'state', 'body', 'detailUrl(cmd+click)'], 68 | colWidths: [20, 10, 40, 60], 69 | wordWrap: true 70 | }); 71 | dataItems.forEach(function (item) { 72 | dataTable_3.push([item.title, item.state, item.body || 'empty description', item.html_url]); 73 | }); 74 | console.log(dataTable_3.toString()); 75 | } 76 | else { 77 | output_1.info('there is no issue match your search condition'); 78 | } 79 | }); 80 | }, 81 | // search users 82 | searchUsers: function (options) { 83 | return request_1.request('/search/users', 'get', options.data, { 84 | headers: { 85 | 'Accept': acceptType 86 | } 87 | }).then(function (res) { 88 | var dataItems = res.data.items; 89 | if (dataItems && dataItems.length > 0) { 90 | var dataTable_4 = tableShow_1.default({ 91 | head: ['name', 'detailUrl(cmd+click)'], 92 | colWidths: [20, 60] 93 | }); 94 | dataItems.forEach(function (item) { 95 | dataTable_4.push([item.login, item.html_url]); 96 | }); 97 | console.log(dataTable_4.toString()); 98 | } 99 | else { 100 | output_1.info('there is no user match your search condition'); 101 | } 102 | }); 103 | }, 104 | // search code 105 | searchCode: function (options) { 106 | return request_1.request('/search/code', 'get', options.data, { 107 | headers: { 108 | 'Accept': acceptType 109 | } 110 | }).then(function (res) { 111 | var dataItems = res.data.items; 112 | if (dataItems && dataItems.length > 0) { 113 | var dataTable_5 = tableShow_1.default({ 114 | head: ['path', 'repos name', 'repos description', 'detailUrl(cmd+click)'], 115 | colWidths: [20, 20, 40, 60], 116 | wordWrap: true 117 | }); 118 | dataItems.forEach(function (item) { 119 | dataTable_5.push([item.path, item.repository.full_name, item.repository.description, item.html_url]); 120 | }); 121 | console.log(dataTable_5.toString()); 122 | } 123 | else { 124 | output_1.info('there is no result match your search condition'); 125 | } 126 | }); 127 | } 128 | }; 129 | function searchWithData(questionObject, fn, keyword) { 130 | askQuestion_1.default([{ 131 | type: 'checkbox', 132 | name: 'filters', 133 | message: 'you could select some filter conditions to search:', 134 | choices: Object.keys(questionObject) 135 | }], function (answers) { 136 | var questionArray = answers.filters.map(function (item) { 137 | return questionObject[item]; 138 | }); 139 | askQuestion_1.default(questionArray, function (theanswers) { 140 | var conditionStr = Object.keys(theanswers).map(function (item) { 141 | return item === 'topic' ? theanswers.topic.split(' ').map(function (topicItem) { 142 | return "topic:" + topicItem; 143 | }).join('+') : item + ":" + theanswers[item]; 144 | }).join('+'); 145 | fn({ 146 | data: { 147 | q: keyword ? keyword + "+" + conditionStr : conditionStr 148 | } 149 | }); 150 | }); 151 | }); 152 | } 153 | exports.searchStrategy = { 154 | 'r': function () { 155 | var questionObject = { 156 | 'fork(filter whether forked repositories should be included)': { 157 | type: 'confirm', 158 | name: 'fork', 159 | message: 'please confirm whether forked repositories should be included:' 160 | }, 161 | 'forks(filter repositoties based on the number of forks)': { 162 | type: 'input', 163 | name: 'forks', 164 | message: 'please input the minimal number of forks to filter:' 165 | }, 166 | 'language(filter repositories based on the language they are written in)': { 167 | type: 'input', 168 | name: 'language', 169 | message: 'please input the languages of searched repositories:' 170 | }, 171 | 'user(limit search action to a specific user)': { 172 | type: 'input', 173 | name: 'user', 174 | message: 'please input the ownername of searched repositories:' 175 | }, 176 | 'size(Finds repositories that match a certain size)': { 177 | type: 'input', 178 | name: 'size', 179 | message: 'please input the minimal size of searched repositories(in kb):' 180 | }, 181 | 'stars(Searches repositories based on the number of stars)': { 182 | type: 'input', 183 | name: 'stars', 184 | message: 'please input the minimal number of stars of searched repositories:' 185 | }, 186 | 'topic(Filters repositories based on the specified topic)': { 187 | type: 'input', 188 | name: 'topic', 189 | message: 'input the topics of searched repositories(split with space):', 190 | } 191 | }; 192 | searchWithData(questionObject, exports.searchActions.searchRepos); 193 | }, 194 | 'c': function () { 195 | var questionObject = { 196 | 'author(Matches commits authored by a user)': { 197 | type: 'input', 198 | name: 'author-name', 199 | message: 'please input the commit author name:' 200 | }, 201 | 'committer(Matches commits committed by a user)': { 202 | type: 'input', 203 | name: 'committer-name', 204 | message: 'please input the committer name:' 205 | }, 206 | 'merge(Matched commits whether be merged)': { 207 | type: 'confirm', 208 | name: 'merge', 209 | message: 'does you need filter merged commits' 210 | }, 211 | 'hash(Matches commits by hash)': { 212 | type: 'input', 213 | name: 'hash', 214 | message: 'please input the hash value:' 215 | }, 216 | 'repo( Limits searches to a specific repository)': { 217 | type: 'input', 218 | name: 'repo', 219 | message: 'please input the repository name:' 220 | } 221 | }; 222 | searchWithData(questionObject, exports.searchActions.searchCommits); 223 | }, 224 | 'i': function () { 225 | var questionObject = { 226 | 'type(With this qualifier you can restrict the search to issues or pull request)': { 227 | type: 'list', 228 | name: 'type', 229 | message: 'please select a search type:', 230 | choices: ['issue', 'pr'] 231 | }, 232 | 'author(Finds issues or pull requests created by a certain user)': { 233 | type: 'input', 234 | name: 'author', 235 | message: 'please input the author name to search:' 236 | }, 237 | 'assignee(Finds issues or pull requests that are assigned to a certain user)': { 238 | type: 'input', 239 | name: 'assignee', 240 | message: 'please input the assignee name to search:' 241 | }, 242 | 'mentions(Finds issues or pull requests that mention a certain user)': { 243 | type: 'input', 244 | name: 'mentions', 245 | message: 'please input the username mentioned to search:' 246 | }, 247 | 'commenter(Finds issues or pull requests that a certain user commented on)': { 248 | type: 'input', 249 | name: 'commenter', 250 | message: 'please input a commenter name to search:' 251 | }, 252 | 'state(Filter issues or pull requests based on whether they are open or closed)': { 253 | type: 'list', 254 | name: 'state', 255 | message: 'please select a state to search:', 256 | choices: ['open', 'closed'] 257 | }, 258 | 'language(Searches for issues or pull requests within repositories that match a certain language)': { 259 | type: 'input', 260 | name: 'language', 261 | message: 'please input the language that match the repository to search for issues:' 262 | } 263 | }; 264 | searchWithData(questionObject, exports.searchActions.searchIssues); 265 | }, 266 | 'u': function () { 267 | var questionObject = { 268 | 'repos(Filters users based on the number of repositories they have)': { 269 | type: 'input', 270 | name: 'repos', 271 | message: 'please input the minimal number of repositories to search:' 272 | }, 273 | 'language(Search for users that have repositories that match a certain language)': { 274 | type: 'input', 275 | name: 'language', 276 | message: 'please input the language that match the repositories of the searched users:' 277 | }, 278 | 'followers(Filter users based on the number of followers they have)': { 279 | type: 'input', 280 | name: 'followers', 281 | message: 'please input the minimal number of followers to searched users have:' 282 | } 283 | }; 284 | searchWithData(questionObject, exports.searchActions.searchUsers); 285 | } 286 | }; 287 | -------------------------------------------------------------------------------- /src/lib/action/reaction.ts: -------------------------------------------------------------------------------- 1 | import {request} from '../tools/request' 2 | import { getToken, getUserName, selectRepos, selectReposWithMode } from '../tools/verification'; 3 | import promiseCompose from '../tools/promiseCompose'; 4 | import askquestion from '../tools/askQuestion'; 5 | import createTable from '../tools/tableShow'; 6 | import { reposActions } from './repos'; 7 | import { prActions } from './pullrequest'; 8 | import { issueActions } from './issues'; 9 | import { success, info } from '../tools/output'; 10 | import { createChoiceTable } from '../tools/askQuestion'; 11 | import { get, which } from 'node-emoji' 12 | const acceptType = 'application/vnd.github.squirrel-girl-preview+json' 13 | 14 | export const rtActions = { 15 | // list reactions for a commit comment 16 | listForCommitComment (listOptions: any) { 17 | return request(`/repos/${listOptions.ownername}/${listOptions.reposname}/comments/${listOptions.id}/reactions`, 'get', {}, { 18 | headers: { 19 | 'Accept': acceptType 20 | } 21 | }).then((res: any) => { 22 | return res.data 23 | }) 24 | }, 25 | // create reaction for a commit comment 26 | createForCommitComment (createOptions: any) { 27 | return promiseCompose([getToken, () => { 28 | return request(`/repos/${createOptions.ownername}/${createOptions.reposname}/comments/${createOptions.id}/reactions`, 'post', createOptions.data, { 29 | headers: { 30 | 'Accept': acceptType, 31 | 'Authorization': `token ${process.env.githubToken}` 32 | } 33 | }).then((res: any) => { 34 | return res.data 35 | }) 36 | }]) 37 | }, 38 | // list reactions for an issue 39 | listForIssue (listOptions: any) { 40 | return request(`/repos/${listOptions.ownername}/${listOptions.reposname}/issues/${listOptions.number}/reactions`, 'get', {}, { 41 | headers: { 42 | 'Accept': acceptType 43 | } 44 | }).then((res: any) => { 45 | return res.data 46 | }) 47 | }, 48 | // create reaction for an issue 49 | createForIssue (createOptions: any) { 50 | return promiseCompose([getToken, () => { 51 | return request(`/repos/${createOptions.ownername}/${createOptions.reposname}/issues/${createOptions.number}/reactions`, 'post', createOptions.data, { 52 | headers: { 53 | 'Accept': acceptType, 54 | 'Authorization': `token ${process.env.githubToken}` 55 | } 56 | }).then((res: any) => { 57 | return res.data 58 | }) 59 | }]) 60 | }, 61 | // list reactions for an issue comment 62 | listForIssueComment (listOptions: any) { 63 | return request(`/repos/${listOptions.ownername}/${listOptions.reposname}/issues/comments/${listOptions.id}/reactions`, 'get', {}, { 64 | headers: { 65 | 'Accept': acceptType 66 | } 67 | }).then((res: any) => { 68 | return res.data 69 | }) 70 | }, 71 | // create reaction for an issue comment 72 | createForIssueComment (createOptions: any) { 73 | return promiseCompose([getToken, () => { 74 | return request(`/repos/${createOptions.ownername}/${createOptions.reposname}/issues/comments/${createOptions.id}/reactions`, 'post', createOptions.data, { 75 | headers: { 76 | 'Accept': acceptType, 77 | 'Authorization': `token ${process.env.githubToken}` 78 | } 79 | }).then((res: any) => { 80 | return res.data 81 | }) 82 | }]) 83 | }, 84 | // list reactions for a pull request review comment 85 | listForPrReviewComment (listOptions: any) { 86 | return request(`/repos/${listOptions.ownername}/${listOptions.reposname}/pulls/comments/${listOptions.id}/reactions`, 'get', {}, { 87 | headers: { 88 | 'Accept': acceptType 89 | } 90 | }).then((res: any) => { 91 | return res.data 92 | }) 93 | }, 94 | // create reaction for a pull request review comment 95 | createForPrReviewComment (createOptions: any) { 96 | return promiseCompose([getToken, () => { 97 | return request(`/repos/${createOptions.ownername}/${createOptions.reposname}/pulls/comments/${createOptions.id}/reactions`, 'post', createOptions.data, { 98 | headers: { 99 | 'Accept': acceptType, 100 | 'Authorization': `token ${process.env.githubToken}` 101 | } 102 | }).then((res: any) => { 103 | return res.data 104 | }) 105 | }]) 106 | } 107 | } 108 | 109 | const emojiMap: any = { 110 | '+1': '+1', 111 | '-1': '-1', 112 | 'laugh': 'grinning', 113 | 'confused': 'confused', 114 | 'heart': 'heart', 115 | 'hooray': 'tada' 116 | } 117 | const getReaction = function (emoji: string) { 118 | return Object.keys(emojiMap).filter((item: string) => { 119 | return get(emojiMap[item]) === emoji 120 | })[0] 121 | } 122 | 123 | export const reactionStrategy: {[key: string]: any} = { 124 | 'ls': { 125 | '-c': function () { 126 | selectReposWithMode((reposname: string, ownername: string) => { 127 | reposActions.listCommitComments({ 128 | ownername: ownername, 129 | reposname: reposname 130 | }).then((resdata: any) => { 131 | if (resdata.length == 0) { 132 | info('no commit comments existed!') 133 | } else { 134 | let heads = [{ 135 | value: 'id', 136 | type: 'number' 137 | }, { 138 | value: 'content', 139 | type: 'description' 140 | }, { 141 | value: 'detailUrl(cmd+click)', 142 | type: 'url' 143 | }] 144 | askquestion([{ 145 | type: 'list', 146 | name: 'comment', 147 | message: 'please select a commit comment:', 148 | choices: createChoiceTable(heads, resdata.map((item: any) => { 149 | return [String(item.id), item.body, item.html_url] 150 | })) 151 | }], (answers: any) => { 152 | rtActions.listForCommitComment({ 153 | ownername: ownername, 154 | reposname: reposname, 155 | id: answers.comment.split('│')[1].trim() 156 | }).then((resdata: any) => { 157 | if (resdata.length === 0) { 158 | info('no reactions existed for this commit comment!') 159 | } else { 160 | let dataTable: any = createTable({ 161 | head: ['id', 'reaction', 'creator', 'create_date'], 162 | colWidths: [10, 10, 20, 40] 163 | }) 164 | resdata.forEach((item: any) => { 165 | dataTable.push([item.id, get(emojiMap[item.content]), item.user.login, item.created_at]) 166 | }) 167 | console.log(dataTable.toString()) 168 | } 169 | }) 170 | }) 171 | } 172 | }) 173 | }) 174 | }, 175 | '-i': function () { 176 | selectReposWithMode((reposname: string, ownername: string) => { 177 | issueActions.listForRepos({ 178 | ownername: ownername, 179 | reposname: reposname 180 | }).then((resdata: any) => { 181 | if (resdata.length === 0) { 182 | info('no issues existed!') 183 | } else { 184 | let heads = [{ 185 | value: 'number', 186 | type: 'number' 187 | }, { 188 | value: 'title', 189 | type: 'title' 190 | }, { 191 | value: 'content', 192 | type: 'description' 193 | }, { 194 | value: 'detailUrl(cmd+click)', 195 | type: 'url' 196 | }] 197 | askquestion([{ 198 | type: 'list', 199 | name: 'issueItem', 200 | message: 'please select a issue from this list:', 201 | choices: createChoiceTable(heads, resdata.map((item: any) => { 202 | return [String(item.number), item.title, item.body || 'no content', item.html_url] 203 | })) 204 | }], function (answers: any) { 205 | rtActions.listForIssue({ 206 | ownername: ownername, 207 | reposname: reposname, 208 | number: answers.issueItem.split('│')[1].trim() 209 | }).then((resdata: any) => { 210 | if (resdata.length > 0) { 211 | let dataTable: any = createTable({ 212 | head: ['id', 'reaction', 'creator', 'create_date'], 213 | colWidths: [10, 10, 20, 40] 214 | }) 215 | resdata.forEach((item: any) => { 216 | dataTable.push([item.id, get(emojiMap[item.content]), item.user.login, item.created_at]) 217 | }) 218 | console.log(dataTable.toString()) 219 | } else { 220 | info('no reactions existed for this issue') 221 | } 222 | }) 223 | }) 224 | } 225 | }) 226 | }) 227 | }, 228 | '-ic': function () { 229 | selectReposWithMode((reposname: string, ownername: string) => { 230 | issueActions.listCommentsForRepo({ 231 | ownername: ownername, 232 | reposname: reposname 233 | }).then((resdata: any) => { 234 | if (resdata.length === 0) { 235 | info('no issue comments existed!') 236 | } else { 237 | let heads = [{ 238 | value: 'id', 239 | type: 'number' 240 | }, { 241 | value: 'content', 242 | type: 'description' 243 | }, { 244 | value: 'detailUrl(cmd+click)', 245 | type: 'url' 246 | }] 247 | askquestion([{ 248 | type: 'list', 249 | name: 'comment', 250 | message: 'please select a comment:', 251 | choices: createChoiceTable(heads, resdata.map((item: any) => { 252 | return [String(item.id), item.body || 'no content', item.html_url] 253 | })) 254 | }], (answers: any) => { 255 | rtActions.listForIssueComment({ 256 | ownername: ownername, 257 | reposname: reposname, 258 | id: answers.comment.split('│')[1].trim() 259 | }).then((resdata: any) => { 260 | if (resdata.length > 0) { 261 | let dataTable: any = createTable({ 262 | head: ['id', 'reaction', 'creator', 'create_date'], 263 | colWidths: [10, 10, 20, 40] 264 | }) 265 | resdata.forEach((item: any) => { 266 | dataTable.push([item.id, get(emojiMap[item.content]), item.user.login, item.created_at]) 267 | }) 268 | console.log(dataTable.toString()) 269 | } else { 270 | info('no reactions existed for this issue comment') 271 | } 272 | }) 273 | }) 274 | } 275 | }) 276 | }) 277 | }, 278 | '-p': function () { 279 | selectReposWithMode((reposname: string, ownername: string) => { 280 | prActions.listCommentsForRepo({ 281 | ownername: ownername, 282 | reposname: reposname 283 | }).then((resdata: any) => { 284 | if (resdata.length === 0) { 285 | info('no pull request comments existed!') 286 | } else { 287 | let heads = [{ 288 | value: 'id', 289 | type: 'number' 290 | }, { 291 | value: 'content', 292 | type: 'description' 293 | }, { 294 | value: 'detailUrl(cmd+click)', 295 | type: 'url' 296 | }] 297 | askquestion([{ 298 | type: 'list', 299 | name: 'comment', 300 | message: 'please select a comment:', 301 | choices: createChoiceTable(heads, resdata.map((item: any) => { 302 | return [String(item.id), item.body || 'no content', item.html_url] 303 | })) 304 | }], (answers: any) => { 305 | rtActions.listForPrReviewComment({ 306 | ownername: ownername, 307 | reposname: reposname, 308 | id: answers.comment.split('│')[1].trim() 309 | }).then((resdata: any) => { 310 | if (resdata.length > 0) { 311 | let dataTable: any = createTable({ 312 | head: ['id', 'reaction', 'creator', 'create_date'], 313 | colWidths: [10, 10, 20, 40] 314 | }) 315 | resdata.forEach((item: any) => { 316 | dataTable.push([item.id, get(emojiMap[item.content]), item.user.login, item.created_at]) 317 | }) 318 | console.log(dataTable.toString()) 319 | } else { 320 | info('no reactions existed for this review comment') 321 | } 322 | }) 323 | }) 324 | } 325 | }) 326 | }) 327 | } 328 | }, 329 | 'cr': { 330 | '-c': function () { 331 | selectReposWithMode((reposname: string, ownername: string) => { 332 | reposActions.listCommitComments({ 333 | ownername: ownername, 334 | reposname: reposname 335 | }).then((resdata: any) => { 336 | if (resdata.length === 0) { 337 | info('no commit comments existed!') 338 | } else { 339 | let heads = [{ 340 | value: 'id', 341 | type: 'number' 342 | }, { 343 | value: 'content', 344 | type: 'description' 345 | }, { 346 | value: 'detailUrl(cmd+click)', 347 | type: 'url' 348 | }] 349 | askquestion([{ 350 | type: 'list', 351 | name: 'comment', 352 | message: 'please select a commit comment:', 353 | choices: createChoiceTable(heads, resdata.map((item: any) => { 354 | return [String(item.id), item.body || 'no content', item.html_url] 355 | })) 356 | }, { 357 | type: 'list', 358 | name: 'reaction', 359 | message: 'please select a reaction type:', 360 | choices: ['+1', '-1', 'laugh', 'confused', 'heart', 'hooray'].map((item: any) => { 361 | return get(emojiMap[item]) 362 | }) 363 | }], (answers: any) => { 364 | rtActions.createForCommitComment({ 365 | ownername: ownername, 366 | reposname: reposname, 367 | id: answers.comment.split('│')[1].trim(), 368 | data: { 369 | content: getReaction(answers.reaction) 370 | } 371 | }).then((resdata: any) => { 372 | success('create reaction for commit comment success!') 373 | let dataTable: any = createTable({ 374 | head: ['id', 'creator', 'reaction', 'create_date'], 375 | colWidths: [10, 20, 20, 40] 376 | }) 377 | dataTable.push([resdata.id, resdata.user.login, get(emojiMap[resdata.content]), resdata.created_at]) 378 | console.log(dataTable.toString()) 379 | }) 380 | }) 381 | } 382 | }) 383 | }) 384 | }, 385 | '-i': function () { 386 | selectReposWithMode((reposname: string, ownername: string) => { 387 | issueActions.listForRepos({ 388 | ownername: ownername, 389 | reposname: reposname 390 | }).then((resdata: any) => { 391 | if (resdata.length === 0) { 392 | info('no issues existed!') 393 | } else { 394 | let heads = [{ 395 | value: 'number', 396 | type: 'number' 397 | }, { 398 | value: 'title', 399 | type: 'title' 400 | }, { 401 | value: 'content', 402 | type: 'description' 403 | }, { 404 | value: 'detailUrl(cmd+click)', 405 | type: 'url' 406 | }] 407 | askquestion([{ 408 | type: 'list', 409 | name: 'issueItem', 410 | message: 'please select a issue from this list:', 411 | choices: createChoiceTable(heads, resdata.map((item: any) => { 412 | return [String(item.number), item.title, item.body || 'no content', item.html_url] 413 | })) 414 | }, { 415 | type: 'list', 416 | name: 'reaction', 417 | message: 'please select a reaction type:', 418 | choices: ['+1', '-1', 'laugh', 'confused', 'heart', 'hooray'].map((item: any) => { 419 | return get(emojiMap[item]) 420 | }) 421 | }], function (answers: any) { 422 | rtActions.createForIssue({ 423 | ownername: ownername, 424 | reposname: reposname, 425 | number: answers.issueItem.split('│')[1].trim(), 426 | data: { 427 | content: getReaction(answers.reaction) 428 | } 429 | }).then((resdata: any) => { 430 | success('reaction for issue created success!') 431 | let dataTable: any = createTable({ 432 | head: ['id', 'creator', 'reaction', 'create_date'], 433 | colWidths: [10, 20, 20, 40] 434 | }) 435 | dataTable.push([resdata.id, resdata.user.login, get(emojiMap[resdata.content]), resdata.created_at]) 436 | console.log(dataTable.toString()) 437 | }) 438 | }) 439 | } 440 | }) 441 | }) 442 | }, 443 | '-ic': function () { 444 | selectReposWithMode((reposname: string, ownername: string) => { 445 | issueActions.listCommentsForRepo({ 446 | ownername: ownername, 447 | reposname: reposname 448 | }).then((resdata: any) => { 449 | if (resdata.length === 0) { 450 | info('no issue comments existed!') 451 | } else { 452 | let heads = [{ 453 | value: 'id', 454 | type: 'number' 455 | }, { 456 | value: 'content', 457 | type: 'description' 458 | }, { 459 | value: 'detailUrl(cmd+click)', 460 | type: 'url' 461 | }] 462 | askquestion([{ 463 | type: 'list', 464 | name: 'comment', 465 | message: 'please select a comment:', 466 | choices: createChoiceTable(heads, resdata.forEach((item: any) => { 467 | return [String(item.id), item.body, item.html_url] 468 | })) 469 | }, { 470 | type: 'list', 471 | name: 'reaction', 472 | message: 'please select a reaction type:', 473 | choices: ['+1', '-1', 'laugh', 'confused', 'heart', 'hooray'].map((item: any) => { 474 | return get(emojiMap[item]) 475 | }) 476 | }], (answers: any) => { 477 | rtActions.createForIssueComment({ 478 | ownername: ownername, 479 | reposname: reposname, 480 | id: answers.comment.split('│')[1].trim(), 481 | data: { 482 | content: getReaction(answers.reaction) 483 | } 484 | }).then((resdata: any) => { 485 | success('reaction for issue comment created success!') 486 | let dataTable: any = createTable({ 487 | head: ['id', 'creator', 'reaction', 'create_date'], 488 | colWidths: [10, 20, 20, 40] 489 | }) 490 | dataTable.push([resdata.id, resdata.user.login, get(emojiMap[resdata.content]), resdata.created_at]) 491 | console.log(dataTable.toString()) 492 | }) 493 | }) 494 | } 495 | }) 496 | }) 497 | }, 498 | '-p': function () { 499 | selectReposWithMode((reposname: string, ownername: string) => { 500 | prActions.listCommentsForRepo({ 501 | ownername: ownername, 502 | reposname: reposname 503 | }).then((resdata: any) => { 504 | if (resdata.length === 0) { 505 | info('no pull request comments existed!') 506 | } else { 507 | let heads = [{ 508 | value: 'id', 509 | type: 'number' 510 | }, { 511 | value: 'content', 512 | type: 'description' 513 | }, { 514 | value: 'detailUrl(cmd+click)', 515 | type: 'url' 516 | }] 517 | askquestion([{ 518 | type: 'list', 519 | name: 'comment', 520 | message: 'please select a comment:', 521 | choices: createChoiceTable(heads, resdata.map((item: any) => { 522 | return ([String(item.id), item.body, item.html_url]) 523 | })) 524 | }, { 525 | type: 'list', 526 | name: 'reaction', 527 | message: 'please select a reaction type:', 528 | choices: ['+1', '-1', 'laugh', 'confused', 'heart', 'hooray'].map((item: any) => { 529 | return get(emojiMap[item]) 530 | }) 531 | }], (answers: any) => { 532 | rtActions.createForPrReviewComment({ 533 | ownername: ownername, 534 | reposname: reposname, 535 | id: answers.comment.split('│')[1].trim(), 536 | data: { 537 | content: getReaction(answers.reaction) 538 | } 539 | }).then((resdata: any) => { 540 | success('reaction for pull request review comment created success!') 541 | let dataTable: any = createTable({ 542 | head: ['id', 'creator', 'reaction', 'create_date'], 543 | colWidths: [10, 20, 20, 40] 544 | }) 545 | dataTable.push([resdata.id, resdata.user.login, get(emojiMap[resdata.content]), resdata.created_at]) 546 | console.log(dataTable.toString()) 547 | }) 548 | }) 549 | } 550 | }) 551 | }) 552 | } 553 | } 554 | } 555 | -------------------------------------------------------------------------------- /src/lib/action/issues.ts: -------------------------------------------------------------------------------- 1 | import {request} from '../tools/request' 2 | import { getToken, selectRepos, selectReposWithMode } from '../tools/verification'; 3 | import askquestion, {createChoiceTable} from '../tools/askQuestion'; 4 | import createTable from '../tools/tableShow'; 5 | import promiseCompose from '../tools/promiseCompose'; 6 | import { info, success } from '../tools/output'; 7 | const acceptType = 'application/vnd.github.jean-grey-preview+json' 8 | 9 | export const issueActions = { 10 | // list issues 11 | listForUser (listOptions: any) { 12 | return promiseCompose([getToken, () => { 13 | return request(`/user/issues`, 'get', listOptions, { 14 | headers: { 15 | 'Authorization': `token ${process.env.githubToken}`, 16 | 'Accept': acceptType 17 | } 18 | }).then((res: any) => { 19 | return res.data 20 | }) 21 | }]) 22 | }, 23 | // create an issue 24 | create (createOptions: any) { 25 | return promiseCompose([getToken, () => { 26 | return request(`/repos/${createOptions.ownername}/${createOptions.reposname}/issues`, 'post', createOptions.data, { 27 | headers: { 28 | 'Authorization': `token ${process.env.githubToken}`, 29 | 'Accept': acceptType 30 | } 31 | }).then((res: any) => { 32 | return res.data 33 | }) 34 | }]) 35 | }, 36 | // list issues for a repository 37 | listForRepos (listOptions: any) { 38 | return request(`/repos/${listOptions.ownername}/${listOptions.reposname}/issues`, 'get', {}, { 39 | headers: { 40 | 'Accept': acceptType 41 | } 42 | }).then((res: any) => { 43 | return res.data 44 | }) 45 | }, 46 | // get a single issue 47 | getSingleIssue (getOptions: any) { 48 | return request(`/repos/${getOptions.ownername}/${getOptions.reposname}/issues/${getOptions.number}`, 'get', {}, { 49 | headers: { 50 | 'Accept': acceptType 51 | } 52 | }).then((res: any) => { 53 | console.log(res.data) 54 | }) 55 | }, 56 | // edit an issue 57 | editIssue (editOptions: any) { 58 | return promiseCompose([getToken, () => { 59 | return request(`/repos/${editOptions.ownername}/${editOptions.reposname}/issues/${editOptions.number}`, 'patch', editOptions.data, { 60 | headers: { 61 | 'Accept': acceptType, 62 | 'Authorization': `token ${process.env.githubToken}` 63 | } 64 | }).then((res: any) => { 65 | return res.data 66 | }) 67 | }]) 68 | }, 69 | // list assignees 70 | listAssignees (listOptions: any) { 71 | return request(`/repos/${listOptions.ownername}/${listOptions.reposname}/assignees`, 'get', {}).then((res: any) => { 72 | return res.data 73 | }) 74 | }, 75 | // check if a user has permission to be assigned to an issue in this repository 76 | checkAssignee (checkOptions: any) { 77 | return request(`/repos/${checkOptions.ownername}/${checkOptions.reposname}/assignees/${checkOptions.assigneeName}`, 'get', {}) 78 | .then((res: any) => { 79 | return res.data 80 | }) 81 | }, 82 | // add assignees to an issue 83 | addAssignees (addOptions: any) { 84 | return promiseCompose([getToken, () => { 85 | return request(`/repos/${addOptions.ownername}/${addOptions.reposname}/issues/${addOptions.number}/assignees`, 'post', addOptions.data, { 86 | headers: { 87 | 'Authorization': `token ${process.env.githubToken}` 88 | } 89 | }).then((res: any) => { 90 | return res.data 91 | }) 92 | }]) 93 | }, 94 | // delete an assignees from an issue 95 | deleteAssignees (deleteOptions: any) { 96 | return promiseCompose([getToken, () => { 97 | return request(`/repos/${deleteOptions.ownername}/${deleteOptions.reposname}/issues/${deleteOptions.number}/assignees`, 'delete', deleteOptions.data, { 98 | headers: { 99 | 'Authorization': `token ${process.env.githubToken}` 100 | } 101 | }) 102 | }]) 103 | }, 104 | // list comments on an issue 105 | listComments (listOptions: any) { 106 | return request(`/repos/${listOptions.ownername}/${listOptions.reposname}/issues/${listOptions.number}/comments`, 'get', {}) 107 | .then((res: any) => { 108 | return res.data 109 | }) 110 | }, 111 | // list comments in a repository 112 | listCommentsForRepo (listOptions: any) { 113 | return request(`/repos/${listOptions.ownername}/${listOptions.reposname}/issues/comments`, 'get', {}) 114 | .then((res: any) => { 115 | return res.data 116 | }) 117 | }, 118 | // create a comment 119 | createComment (createOptions: any) { 120 | return promiseCompose([getToken, () => { 121 | return request(`/repos/${createOptions.ownername}/${createOptions.reposname}/issues/${createOptions.number}/comments`, 'post', createOptions.data, { 122 | headers: { 123 | 'Authorization': `token ${process.env.githubToken}`, 124 | 'Accept': 'application/vnd.github.machine-man-preview' 125 | } 126 | }).then((res: any) => { 127 | return res.data 128 | }) 129 | }]) 130 | }, 131 | // edit a comment 132 | editComment (editOptions: any) { 133 | return promiseCompose([getToken, () => { 134 | return request(`/repos/${editOptions.ownername}/${editOptions.reposname}/issues/comments/${editOptions.id}`, 'patch', editOptions.data, { 135 | headers: { 136 | 'Authorization': `token ${process.env.githubToken}`, 137 | 'Accept': 'application/vnd.github.machine-man-preview' 138 | } 139 | }).then((res: any) => { 140 | return res.data 141 | }) 142 | }]) 143 | }, 144 | // delete a comment 145 | deleteComment (deleteOptions: any) { 146 | return promiseCompose([getToken, () => { 147 | return Promise.all(deleteOptions.ids.map((item: any) => { 148 | return request(`/repos/${deleteOptions.ownername}/${deleteOptions.reposname}/issues/comments/${item}`, 'delete', {}, { 149 | headers: { 150 | 'Authorization': `token ${process.env.githubToken}`, 151 | 'Accept': 'application/vnd.github.machine-man-preview' 152 | } 153 | }) 154 | })).then((res: any) => { 155 | return res 156 | }) 157 | }]) 158 | }, 159 | // add labels to an issue 160 | addLabelsForIssue (addOptions: any) { 161 | return promiseCompose([getToken, () => { 162 | return request(`/repos/${addOptions.ownername}/${addOptions.reposname}/issues/${addOptions.number}/labels`, 'post', addOptions.data, { 163 | headers: { 164 | 'Authorization': `token ${process.env.githubToken}`, 165 | 'Accept': acceptType 166 | } 167 | }).then((res: any) => { 168 | return res.data 169 | }) 170 | }]) 171 | }, 172 | // replace all labels for an issue 173 | replaceLabelsForIssue (replaceOptions: any) { 174 | return promiseCompose([getToken, () => { 175 | return request(`/repos/${replaceOptions.ownername}/${replaceOptions.reposname}/issues/${replaceOptions.number}/labels`, 'put', replaceOptions.data, { 176 | headers: { 177 | 'Authorization': `token ${process.env.githubToken}`, 178 | 'Accept': acceptType 179 | } 180 | }).then((res: any) => { 181 | return res.data 182 | }) 183 | }]) 184 | }, 185 | // list labels on an issue 186 | listLabelsForIssue (listOptions: any) { 187 | return request(`/repos/${listOptions.ownername}/${listOptions.reposname}/issues/${listOptions.number}/labels`, 'get', {}, { 188 | headers: { 189 | 'Accept': acceptType 190 | } 191 | }).then((res: any) => { 192 | return res.data 193 | }) 194 | }, 195 | // remove a label from an issue 196 | removeLabelForIssue (deleteOptions: any) { 197 | return promiseCompose([getToken, () => { 198 | return Promise.all(deleteOptions.labelNames.map((item: any) => { 199 | return request(`/repos/${deleteOptions.ownername}/${deleteOptions.reposname}/issues/${deleteOptions.number}/labels/${item}`, 'delete', {}, { 200 | headers: { 201 | 'Authorization': `token ${process.env.githubToken}`, 202 | 'Accept': acceptType 203 | } 204 | }) 205 | })).then((res: any) => { 206 | return res.data 207 | }) 208 | }]) 209 | }, 210 | // remove all labels from an issue 211 | removeLabelsForIssue (deleteOptions: any) { 212 | return promiseCompose([getToken, () => { 213 | return request(`/repos/${deleteOptions.ownername}/${deleteOptions.reposname}/issues/${deleteOptions.number}/labels`, 'delete', {}, { 214 | headers: { 215 | 'Authorization': `token ${process.env.githubToken}`, 216 | 'Accept': acceptType 217 | } 218 | }) 219 | }]) 220 | } 221 | } 222 | 223 | const selectIssue = function (fn: Function) { 224 | selectReposWithMode((reposname: string, targetName: string) => { 225 | issueActions.listForRepos({ 226 | ownername: targetName, 227 | reposname: reposname 228 | }).then((resdata: any) => { 229 | if (resdata.length > 0) { 230 | let heads: any = [{ 231 | value: 'number', 232 | type: 'number' 233 | }, { 234 | value: 'title', 235 | type: 'title' 236 | }, { 237 | value: 'content', 238 | type: 'description' 239 | }, { 240 | value: 'detailUrl(cmd+click)', 241 | type: 'url' 242 | }] 243 | askquestion([{ 244 | type: 'list', 245 | name: 'issueItem', 246 | message: 'please select a issue from this list:', 247 | choices: createChoiceTable(heads, resdata.map((item: any) => { 248 | return [String(item.number), item.title, item.body || 'no content', item.html_url] 249 | })) 250 | }], function (answers: any) { 251 | fn(targetName, reposname, answers.issueItem.split('│')[1].trim()) 252 | }) 253 | } else { 254 | info('no issues existed!please create it first') 255 | } 256 | }) 257 | }) 258 | } 259 | 260 | export const issueStrategies: any = { 261 | 'ls': { 262 | '-u': function () { 263 | askquestion([{ 264 | type: 'list', 265 | name: 'filter', 266 | message: 'which type of issues you want to see:(default assigned)', 267 | choices: ['assigned to you', 'created by you', 'mentioned you', 'subscribed by you', 'all of these'] 268 | }, { 269 | type: 'list', 270 | name: 'state', 271 | message: 'which state of issues you want to see:(default open)', 272 | choices: ['open', 'closed', 'all'] 273 | }, { 274 | type: 'list', 275 | name: 'sort', 276 | message: 'which type of sort rule you want to display:(default created)', 277 | choices: ['created', 'updated', 'comments'] 278 | }], function (answers: any) { 279 | issueActions.listForUser({ 280 | filter: answers.filter.split(' ')[0], 281 | state: answers.state, 282 | sort: answers.sort 283 | }).then((resdata: any) => { 284 | if (resdata.length === 0) { 285 | info('no issues existed!') 286 | } else { 287 | let dataTable: any = createTable({ 288 | head: ['number', 'title', 'creator', 'state', 'detailUrl(cmd+click)'], 289 | colWidths: [10, 20, 20, 10, 60] 290 | }) 291 | resdata.forEach((item: any) => { 292 | dataTable.push([item.number, item.title, item.user.login, item.state, item.html_url]) 293 | }) 294 | console.log(dataTable.toString()) 295 | } 296 | }) 297 | }) 298 | }, 299 | '-r': function () { 300 | selectReposWithMode((reposname: string, targetName: string) => { 301 | issueActions.listForRepos({ 302 | ownername: targetName, 303 | reposname: reposname 304 | }).then((resdata: any) => { 305 | if (resdata.length === 0) { 306 | info('no issues existed!') 307 | } else { 308 | let dataTable: any = createTable({ 309 | head: ['title', 'state', 'content', 'creator', 'detailUr(cmd+click)'], 310 | colWidths: [20, 10, 40, 10, 50], 311 | wordWrap: true 312 | }) 313 | resdata.forEach((item: any) => { 314 | dataTable.push([item.title, item.state, item.body || 'no content', item.user.login, item.html_url]) 315 | }) 316 | console.log(dataTable.toString()) 317 | } 318 | }) 319 | }) 320 | }, 321 | '-a': function () { 322 | selectReposWithMode((reposname: string, targetName: string) => { 323 | issueActions.listAssignees({ 324 | ownername: targetName, 325 | reposname: reposname 326 | }).then((resdata: any) => { 327 | if (resdata.length > 0) { 328 | let dataTable: any = createTable({ 329 | head: ['name', 'detailUrl(cmd+click)'], 330 | colWidths: [20, 60] 331 | }) 332 | resdata.forEach((item: any) => { 333 | dataTable.push([item.login, item.html_url]) 334 | }) 335 | console.log(dataTable.toString()) 336 | } else { 337 | info('no assignees existed!') 338 | } 339 | }) 340 | }) 341 | }, 342 | '-c': function () { 343 | selectIssue(function (targetName: string, reposname: string, theIssueNumber: number) { 344 | issueActions.listComments({ 345 | ownername: targetName, 346 | reposname: reposname, 347 | number: theIssueNumber, 348 | data: {} 349 | }).then((resdata: any) => { 350 | if (resdata.length === 0) { 351 | info('no comments existed!') 352 | } else { 353 | let dataTable: any = createTable({ 354 | head: ['id', 'author', 'content', 'detailUrl(cmd+click)'], 355 | colWidths: [10, 20, 40, 60], 356 | wordWrap: true 357 | }) 358 | resdata.forEach((item: any) => { 359 | dataTable.push([item.id, item.user.login, item.body, item.html_url]) 360 | }) 361 | console.log(dataTable.toString()) 362 | } 363 | }) 364 | }) 365 | }, 366 | '-cr': function () { 367 | selectReposWithMode((reposname: string, targetName: string) => { 368 | issueActions.listCommentsForRepo({ 369 | ownername: targetName, 370 | reposname: reposname 371 | }).then((resdata: any) => { 372 | if (resdata.length === 0) { 373 | info('no comments existed!') 374 | } else { 375 | let dataTable: any = createTable({ 376 | head: ['id', 'author', 'content', 'detailUrl(cmd+click)'], 377 | colWidths: [10, 20, 40, 50], 378 | wordWrap: true 379 | }) 380 | resdata.forEach((item: any) => { 381 | dataTable.push([item.id, item.user.login, item.body, item.html_url]) 382 | }) 383 | console.log(dataTable.toString()) 384 | } 385 | }) 386 | }) 387 | } 388 | }, 389 | 'cr': { 390 | '-r': function () { 391 | selectReposWithMode((reposname: string, targetName: string) => { 392 | askquestion([{ 393 | type: 'input', 394 | name: 'title', 395 | message: 'please input the title of this issue:' 396 | }, { 397 | type: 'editor', 398 | name: 'content', 399 | message: 'please input the content of this issue' 400 | }], function (answers: any) { 401 | issueActions.create({ 402 | ownername: targetName, 403 | reposname: reposname, 404 | data: { 405 | title: answers.title, 406 | body: answers.content 407 | } 408 | }).then((resdata: any) => { 409 | success('create issues success!') 410 | let dataTable: any = createTable({ 411 | head: ['number', 'title', 'content', 'detailUrl(cmd+click)'], 412 | colWidths: [10, 20, 20, 40, 40], 413 | wordWrap: true 414 | }) 415 | dataTable.push([resdata.number, resdata.title, resdata.body, resdata.html_url]) 416 | console.log(dataTable.toString()) 417 | }) 418 | }) 419 | }) 420 | }, 421 | '-a': function () { 422 | selectIssue(function (ownername: string, reposname: string, issuenumber: number) { 423 | askquestion([{ 424 | type: 'input', 425 | name: 'assignees', 426 | message: 'please input some assignees of this issue(split with space):' 427 | }], function (answers: any) { 428 | issueActions.addAssignees({ 429 | ownername: ownername, 430 | reposname: reposname, 431 | number: issuenumber, 432 | data: { 433 | assignees: answers.assignees.split(' ') 434 | } 435 | }).then((resdata: any) => { 436 | success('add assignees success!') 437 | }) 438 | }) 439 | }) 440 | }, 441 | '-c': function () { 442 | selectIssue(function (ownername: string, reposname: string, issuenumber: number) { 443 | askquestion([{ 444 | type: 'editor', 445 | name: 'content', 446 | message: 'please input the content of this comment for the issue:' 447 | }], function (answers: any) { 448 | issueActions.createComment({ 449 | ownername: ownername, 450 | reposname: reposname, 451 | number: issuenumber, 452 | data: { 453 | body: answers.content 454 | } 455 | }).then((resdata: any) => { 456 | success('create comment success!') 457 | let dataTable: any = createTable({ 458 | head: ['id', 'content', 'detailUrl(cmd+click)'], 459 | colWidths: [20, 40, 60], 460 | wordWrap: true 461 | }) 462 | dataTable.push([resdata.id, resdata.body, resdata.html_url]) 463 | console.log(dataTable.toString()) 464 | }) 465 | }) 466 | }) 467 | }, 468 | '-l': function () { 469 | selectIssue(function (ownername: string, reposname: string, issuenumber: number) { 470 | askquestion([{ 471 | type: 'input', 472 | name: 'labels', 473 | message: 'please input the names of the labels to be added to this issue(split with space):' 474 | }], function (answers: any) { 475 | issueActions.addLabelsForIssue({ 476 | ownername: ownername, 477 | reposname: reposname, 478 | number: issuenumber, 479 | data: answers.labels.split(' ') 480 | }).then((resdata: any) => { 481 | success('add labels to issue success!') 482 | let dataTable: any = createTable({ 483 | head: ['id', 'name', 'color'], 484 | colWidths: [20, 20, 20] 485 | }) 486 | resdata.forEach((item: any) => { 487 | dataTable.push([item.id, item.name, item.color]) 488 | }) 489 | console.log(dataTable.toString()) 490 | }) 491 | }) 492 | }) 493 | } 494 | }, 495 | 'et': { 496 | '-i': function () { 497 | selectIssue(function (ownername: string, reposname: string, issuenumber: number) { 498 | askquestion([{ 499 | type: 'input', 500 | name: 'title', 501 | message: 'please edit the title of this issue' 502 | }, { 503 | type: 'editor', 504 | name: 'content', 505 | message: 'please edit the content of this issue' 506 | }], function (answers: any) { 507 | issueActions.editIssue({ 508 | ownername: ownername, 509 | reposname: reposname, 510 | number: issuenumber, 511 | data: { 512 | title: answers.title, 513 | body: answers.content 514 | } 515 | }).then((resdata: any) => { 516 | success('update issue success!') 517 | let dataTable: any = createTable({ 518 | head: ['number', 'title', 'content', 'detailUrl(cmd+click)'], 519 | colWidths: [10, 20, 40, 60], 520 | wordWrap: true 521 | }) 522 | dataTable.push([resdata.number, resdata.title, resdata.body, resdata.html_url]) 523 | console.log(dataTable.toString()) 524 | }) 525 | }) 526 | }) 527 | }, 528 | '-c': function () { 529 | selectReposWithMode((reposname: string, targetName: string) => { 530 | issueActions.listCommentsForRepo({ 531 | ownername: targetName, 532 | reposname: reposname 533 | }).then((resdata: any) => { 534 | if (resdata.length > 0) { 535 | let heads = [{ 536 | value: 'id', 537 | type: 'number' 538 | }, { 539 | value: 'content', 540 | type: 'description' 541 | }, { 542 | value: 'detailUrl(cmd+click)', 543 | type: 'url' 544 | }] 545 | askquestion([{ 546 | type: 'list', 547 | name: 'comment', 548 | message: 'please select a comment:', 549 | choices: createChoiceTable(heads, resdata.map((item: any) => { 550 | return [String(item.id), item.body, item.html_url] 551 | })) 552 | }, { 553 | type: 'editor', 554 | name: 'content', 555 | message: 'please edit the content of this comment' 556 | }], function (answers: any) { 557 | issueActions.editComment({ 558 | ownername: targetName, 559 | reposname: reposname, 560 | id: answers.comment.split('│')[1].trim(), 561 | data: { 562 | body: answers.content 563 | } 564 | }).then((resdata: any) => { 565 | success('update this comment success!') 566 | let dataTable: any = createTable({ 567 | head: ['id', 'content', 'detailUrl(cmd+click)'], 568 | colWidths: [10, 40, 60], 569 | wordWrap: true 570 | }) 571 | dataTable.push([resdata.id, resdata.body, resdata.html_url]) 572 | console.log(dataTable.toString()) 573 | }) 574 | }) 575 | } else { 576 | info('no comments existed!you need create it first') 577 | } 578 | }) 579 | }) 580 | }, 581 | '-r': function () { 582 | selectIssue(function (ownername: string, reposname: string, issuenumber: number) { 583 | askquestion([{ 584 | type: 'input', 585 | name: 'labels', 586 | message: 'please input some label names to replace those existed:' 587 | }], function (answers: any) { 588 | issueActions.replaceLabelsForIssue({ 589 | ownername: ownername, 590 | reposname: reposname, 591 | number: issuenumber, 592 | data: answers.labels.split(' ') 593 | }).then((resdata: any) => { 594 | success('replace labels success!') 595 | let dataTable: any = createTable({ 596 | head: ['id', 'name', 'color'], 597 | colWidths: [10, 20, 20] 598 | }) 599 | resdata.forEach((item: any) => { 600 | dataTable.push([item.id, item.name, item.color]) 601 | }) 602 | console.log(dataTable.toString()) 603 | }) 604 | }) 605 | }) 606 | } 607 | }, 608 | 'rm': { 609 | '-a': function () { 610 | selectIssue(function (ownername: string, reposname: string, issuenumber: number) { 611 | issueActions.listAssignees({ 612 | ownername: ownername, 613 | reposname: reposname 614 | }).then((resdata: any) => { 615 | if (resdata.length > 0) { 616 | askquestion([{ 617 | type: 'checkbox', 618 | name: 'assignees', 619 | message: 'please select some assignees to be removed:', 620 | choices: resdata.map((item: any) => { 621 | return item.login 622 | }) 623 | }], function (answers: any) { 624 | issueActions.deleteAssignees({ 625 | ownername: ownername, 626 | reposname: reposname, 627 | number: issuenumber, 628 | data: { 629 | assignees: answers.assignees 630 | } 631 | }).then((res: any) => { 632 | success('remove assignees success!') 633 | }) 634 | }) 635 | } else { 636 | info('no assignees available to be removed!') 637 | } 638 | }) 639 | }) 640 | }, 641 | '-c': function () { 642 | selectReposWithMode(function (reposname: string, targetName: string) { 643 | issueActions.listCommentsForRepo({ 644 | ownername: targetName, 645 | reposname: reposname 646 | }).then((resdata: any) => { 647 | if (resdata.length > 0) { 648 | let heads = [{ 649 | value: 'id', 650 | type: 'number' 651 | }, { 652 | value: 'content', 653 | type: 'description' 654 | }, { 655 | value: 'detailUrl(cmd+click)', 656 | type: 'url' 657 | }] 658 | askquestion([{ 659 | type: 'checkbox', 660 | name: 'comments', 661 | message: 'please select some comments to be removed:', 662 | choices: createChoiceTable(heads, resdata.map((item: any) => { 663 | return [String(item.id), item.body, item.html_url] 664 | })) 665 | }], function (answers: any) { 666 | issueActions.deleteComment({ 667 | ownername: targetName, 668 | reposname: reposname, 669 | ids: answers.comments.map((item: any) => { 670 | return item.split('│')[1].trim() 671 | }) 672 | }) 673 | }) 674 | } else { 675 | info('no comments existed!you need create it first') 676 | } 677 | }) 678 | }) 679 | }, 680 | '-l': function () { 681 | selectIssue(function (ownername: string, reposname: string, issuenumber: number) { 682 | askquestion([{ 683 | type: 'confirm', 684 | name: 'removeall', 685 | message: 'do you need remove all the labels?' 686 | }], function (answers: any) { 687 | if (answers.removeall) { 688 | issueActions.removeLabelsForIssue({ 689 | ownername: ownername, 690 | reposname: reposname, 691 | number: issuenumber 692 | }) 693 | } else { 694 | issueActions.listLabelsForIssue({ 695 | ownername: ownername, 696 | reposname: reposname, 697 | number: issuenumber 698 | }).then((resdata: any) => { 699 | if (resdata.length > 0) { 700 | let heads = [{ 701 | value: 'id', 702 | type: 'number' 703 | }, { 704 | value: 'name', 705 | type: 'title' 706 | }, { 707 | value: 'detailUrl(cmd+click)', 708 | type: 'url' 709 | }] 710 | askquestion([{ 711 | type: 'checkbox', 712 | name: 'labels', 713 | message: 'please select some labels to be removed:', 714 | choices: createChoiceTable(heads, resdata.map((item: any) => { 715 | return [String(item.id), item.name, item.html_url] 716 | })) 717 | }], function (answers: any) { 718 | issueActions.removeLabelForIssue({ 719 | ownername: ownername, 720 | reposname: reposname, 721 | number: issuenumber, 722 | labelNames: answers.labels.map((item: any) => { 723 | return item.split('│')[2].trim() 724 | }) 725 | }) 726 | }) 727 | } else { 728 | info('no labels existed! you have nothing to remove') 729 | } 730 | }) 731 | } 732 | }) 733 | }) 734 | } 735 | } 736 | } 737 | --------------------------------------------------------------------------------