├── cli.ts ├── bin └── kibibit-cli.js ├── .gitignore ├── lib ├── errors │ ├── error-handler.js │ └── KbError.js ├── kb-info.js ├── kb-git-root.js ├── kibibit.js ├── gitflow │ ├── master.js │ ├── develop.js │ ├── init.js │ ├── finish.js │ ├── sync.js │ ├── hotfix.js │ ├── feature.js │ ├── utility.js │ ├── release.js │ ├── commit.js │ └── status.js ├── services │ ├── kb-user.js │ ├── kb-github.js │ └── kb-repo.js ├── kb-string.js └── commandDefinitions.js ├── package.json ├── .eslintrc ├── README.md └── tsconfig.json /cli.ts: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /bin/kibibit-cli.js: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env node 2 | require('manakin').global; 3 | 4 | var kibibit = require('../lib/kibibit.js'); 5 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | 2 | node_modules/ 3 | logs/ 4 | mochawesome-reports/ 5 | coverage/ 6 | .nyc_output/ 7 | .idea/ 8 | 9 | monkeyDB.json 10 | 11 | mochawesome-report/ 12 | 13 | privateConfig.json 14 | test.js 15 | 16 | # dist folder 17 | dist/ 18 | -------------------------------------------------------------------------------- /lib/errors/error-handler.js: -------------------------------------------------------------------------------- 1 | /*eslint-env es6*/ 2 | var _ = require('lodash'); 3 | 4 | module.exports = function errorHandlerClosure(prefix) { 5 | // console.log('error handler created for ', prefix); 6 | return function errorHandler(error) { 7 | var logError = error.exitStatus === 0 || error.noTrace ? 8 | console.error : console.trace; 9 | var exitStatus = _.isNumber(error.exitStatus) ? error.exitStatus : 1; 10 | 11 | prefix = _.isString(prefix) ? _.trim(prefix) + ' ' : ''; 12 | 13 | logError(prefix + error); 14 | 15 | process.exit(exitStatus); 16 | }; 17 | }; 18 | -------------------------------------------------------------------------------- /lib/kb-info.js: -------------------------------------------------------------------------------- 1 | var os = require('os'); 2 | var kbString = require('kb-string'); 3 | var gitRoot = require('kb-git-root'); 4 | 5 | module.export = info; 6 | 7 | var info = { 8 | runFolder: process.cwd(), 9 | gitRoot: gitRoot, 10 | os: { 11 | username: os.userInfo().username, 12 | hostname: os.hostname(), 13 | type: os.type() 14 | }, 15 | signInAnimation: [ 16 | kbString.info(kbString.warning('/'), ' Signing in'), 17 | kbString.info(kbString.warning('|'), ' Signing in..'), 18 | kbString.info(kbString.warning('\\'), ' Signing in..'), 19 | kbString.info(kbString.warning('-'), ' Signing in...') 20 | ] 21 | }; 22 | -------------------------------------------------------------------------------- /lib/errors/KbError.js: -------------------------------------------------------------------------------- 1 | /*eslint-env es6*/ 2 | var _ = require('lodash'); 3 | 4 | module.exports = class KbError extends Error { 5 | constructor(message, exitStatus, noTrace) { 6 | 7 | // Calling parent constructor of base Error class. 8 | super(message); 9 | 10 | // Saving class name in the property of our custom error as a shortcut. 11 | this.name = this.constructor.name; 12 | this.noTrace = noTrace || false; 13 | 14 | // Capturing stack trace, excluding constructor call from it. 15 | Error.captureStackTrace(this, this.constructor); 16 | 17 | // process.exit exit status. 18 | // default is to throw an error 19 | // set to 0 if you want to show error gracefully (intentional) 20 | this.exitStatus = _.isNumber(exitStatus) ? exitStatus : 1; 21 | 22 | } 23 | }; 24 | -------------------------------------------------------------------------------- /lib/kb-git-root.js: -------------------------------------------------------------------------------- 1 | var KbError = require('./errors/KbError'); 2 | var currentFolder = process.cwd(); 3 | var path = require('path'); 4 | var fs = require('fs'); 5 | var Q = require('q'); 6 | 7 | var findRoot = require('find-root'); 8 | 9 | var kbGitRoot = {}; 10 | 11 | kbGitRoot.getGitRoot = getGitRoot; 12 | 13 | var deferred = Q.defer(); 14 | 15 | try { 16 | var foundRoot = findRoot(currentFolder, function (dir) { 17 | return fs.existsSync(path.resolve(dir, '.git')); 18 | }); 19 | // console.log('found git root', foundRoot); 20 | deferred.resolve(foundRoot); 21 | } catch (error) { 22 | // console.error('no git root found', error); 23 | // gitRoot = null; 24 | var err = new KbError('git repo not found', 1, true); 25 | deferred.reject(err); 26 | } 27 | 28 | // console.log('this is gitRoot', gitRoot); 29 | 30 | module.exports = kbGitRoot; 31 | 32 | function getGitRoot() { 33 | // console.log('yo'); 34 | return deferred.promise; 35 | } 36 | -------------------------------------------------------------------------------- /lib/kibibit.js: -------------------------------------------------------------------------------- 1 | // var pkginfo = require('pkginfo')(module); 2 | var shell = require('shelljs'); 3 | var program = require('gitlike-cli'); 4 | // var Confirm = require('prompt-confirm'); 5 | // var currentFolder = process.cwd(); 6 | var kbString = require('./kb-string'); 7 | var commandDefinitions = require('./commandDefinitions'); 8 | 9 | if (!shell.which('git')) { 10 | shell.echo('Sorry, kibibit requires git'); 11 | shell.exit(1); 12 | } 13 | 14 | var gitflowUrl = 'https://datasift.github.io/gitflow/IntroducingGitFlow.html'; 15 | 16 | program 17 | .version(module.exports.version) 18 | .description(kbString.build( 19 | kbString.header([ 20 | kbString.success('~= '), 21 | kbString.kibibitLogo(), 22 | ' cli tool for development using hubflow(gitflow) ', 23 | kbString.success('=~') 24 | ]), 25 | kbString.build([ 26 | '\n', 27 | bodyLine('this cli should eventually be used to work on '), 28 | bodyLine('different kibibit projects. read more about gitflow here:'), 29 | bodyLine(kbString.info(gitflowUrl)) 30 | ]) 31 | )); 32 | 33 | commandDefinitions.attach(program); 34 | 35 | program.parse(process.argv); 36 | 37 | process.on('uncaughtException', function (error) { 38 | console.log(error.stack); 39 | }); 40 | 41 | function bodyLine(str) { 42 | return ' ' + str + '\n'; 43 | } 44 | -------------------------------------------------------------------------------- /lib/gitflow/master.js: -------------------------------------------------------------------------------- 1 | // var kbString = require('../kb-string'); 2 | var gitRoot = require('../kb-git-root'); 3 | // var fs = require('fs'); 4 | // var currentFolder = process.cwd(); 5 | var NodeGit = require('nodegit-flow')(require('nodegit')); 6 | // var Table = require('cli-table'); 7 | // var _ = require('lodash'); 8 | // var path = require('path'); 9 | // var colorize = require('json-colorizer'); 10 | // var Q = require('q'); 11 | // var findRoot = require('find-root'); 12 | // var countFiles = require('count-files'); 13 | 14 | var developGitFlow = {}; 15 | 16 | developGitFlow.develop = develop; 17 | 18 | module.exports = developGitFlow; 19 | 20 | var gRepo; 21 | 22 | 23 | function develop() { 24 | gitRoot.getGitRoot() 25 | .then(function(_gitRoot) { 26 | return NodeGit.Repository.open(_gitRoot); 27 | }) 28 | .then(function(repo) { 29 | gRepo = repo; 30 | return NodeGit.Flow.getConfig(repo); 31 | }) 32 | .then(function(config) { 33 | return gRepo.getBranch(config['gitflow.branch.master'] || 'master'); 34 | }) 35 | .then(function(reference) { 36 | //checkout branch 37 | return gRepo.checkoutRef(reference); 38 | }) 39 | .then(function() { 40 | process.exit(0); 41 | }) 42 | .catch(function(error) { 43 | console.error(error); 44 | process.exit(1); 45 | }); 46 | } 47 | -------------------------------------------------------------------------------- /lib/gitflow/develop.js: -------------------------------------------------------------------------------- 1 | // var kbString = require('../kb-string'); 2 | var gitRoot = require('../kb-git-root'); 3 | // var fs = require('fs'); 4 | // var currentFolder = process.cwd(); 5 | var NodeGit = require('nodegit-flow')(require('nodegit')); 6 | // var Table = require('cli-table'); 7 | // var _ = require('lodash'); 8 | // var path = require('path'); 9 | // var colorize = require('json-colorizer'); 10 | // var Q = require('q'); 11 | // var findRoot = require('find-root'); 12 | // var countFiles = require('count-files'); 13 | 14 | var developGitFlow = {}; 15 | 16 | developGitFlow.develop = develop; 17 | 18 | module.exports = developGitFlow; 19 | 20 | var gRepo; 21 | 22 | 23 | function develop() { 24 | gitRoot.getGitRoot() 25 | .then(function(_gitRoot) { 26 | return NodeGit.Repository.open(_gitRoot); 27 | }) 28 | .then(function(repo) { 29 | gRepo = repo; 30 | return NodeGit.Flow.getConfig(repo); 31 | }) 32 | .then(function(config) { 33 | return gRepo.getBranch(config['gitflow.branch.develop'] || 'develop'); 34 | }) 35 | .then(function(reference) { 36 | //checkout branch 37 | return gRepo.checkoutRef(reference); 38 | }) 39 | .then(function() { 40 | process.exit(0); 41 | }) 42 | .catch(function(error) { 43 | console.error(error); 44 | process.exit(1); 45 | }); 46 | } 47 | -------------------------------------------------------------------------------- /lib/gitflow/init.js: -------------------------------------------------------------------------------- 1 | var kbString = require('../kb-string'); 2 | var gitRoot = require('../kb-git-root'); 3 | var kbUser = require('../services/kb-user'); 4 | var kbRepo = require('../services/kb-repo'); 5 | var errorHandler = require('../errors/error-handler')('[INIT-FLOW]'); 6 | var currentFolder = process.cwd(); 7 | 8 | var initGitflow = {}; 9 | 10 | initGitflow.init = init; 11 | 12 | module.exports = initGitflow; 13 | 14 | function init() { 15 | var GLOBAL = {}; 16 | 17 | gitRoot.getGitRoot() 18 | .then(function(root) { 19 | GLOBAL.root = root; 20 | 21 | return kbRepo.openGit(); 22 | }) 23 | .catch(function() { 24 | console.info(kbString.error('git repo not found')); 25 | console.log('initializing...'); 26 | 27 | GLOBAL.root = currentFolder; 28 | 29 | return kbRepo.initGit(GLOBAL.root); 30 | }) 31 | .then(function(repo) { 32 | GLOBAL.repo = repo; 33 | 34 | return kbRepo.ensureGitFlowNotInitialized(GLOBAL.repo); 35 | }) 36 | .then(kbString.printFlash) 37 | .then(kbUser.questions.selectOrCreateUser) 38 | .then(function(user) { 39 | GLOBAL.user = user; 40 | return kbRepo.setGitConfigUser(GLOBAL.repo, GLOBAL.user); 41 | }) 42 | .then(function() { 43 | return kbRepo.ensureBasicGitFlowDetails(GLOBAL.root, 44 | GLOBAL.repo, GLOBAL.user); 45 | }) 46 | .then(function() { 47 | return kbRepo.questions.createRepoOrigin(GLOBAL.repo, GLOBAL.user); 48 | }) 49 | .then(function() { 50 | console.log('git flow repo initialized'); 51 | process.exit(0); 52 | }) 53 | .catch(errorHandler); 54 | } 55 | -------------------------------------------------------------------------------- /package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "kibibit-cli", 3 | "version": "0.1.0", 4 | "description": "cli tool for development using hubflow(gitflow + GitHub)", 5 | "bin": { 6 | "kibibit": "bin/kibibit-cli.js" 7 | }, 8 | "main": "./lib/kibibit.js", 9 | "scripts": { 10 | "test": "echo \"Error: no test specified\" && exit 1" 11 | }, 12 | "repository": { 13 | "type": "git", 14 | "url": "git+https://github.com/Kibibit/kibibit-cli.git" 15 | }, 16 | "author": "neilkalman@gmail.com", 17 | "license": "ISC", 18 | "bugs": { 19 | "url": "https://github.com/Kibibit/kibibit-cli/issues" 20 | }, 21 | "homepage": "https://github.com/Kibibit/kibibit-cli#readme", 22 | "dependencies": { 23 | "async": "^2.6.0", 24 | "blessed": "^0.1.81", 25 | "cardinal": "^1.0.0", 26 | "chalk": "^2.3.0", 27 | "cli-color": "^1.2.0", 28 | "cli-table": "^0.3.1", 29 | "count-files": "^2.6.2", 30 | "eslint": "^5.14.1", 31 | "eslint-plugin-kibibit": "github:kibibit/eslint-plugin-kibibit", 32 | "eslint-plugin-lodash": "^5.1.0", 33 | "find-root": "^1.1.0", 34 | "fs-readfile-promise": "^3.0.1", 35 | "gitlike-cli": "^0.1.0", 36 | "highlight.js": "^9.14.2", 37 | "home-config": "^0.1.0", 38 | "indent-string": "^3.2.0", 39 | "inquirer": "^4.0.0", 40 | "json-colorizer": "^2.1.2", 41 | "keytar": "^4.1.0", 42 | "lodash": "^4.17.4", 43 | "manakin": "^0.5.1", 44 | "marked": "^0.6.1", 45 | "moment": "^2.19.3", 46 | "node-emoji": "^1.8.1", 47 | "nodegit": "^0.25.0-alpha.7", 48 | "nodegit-flow": "^2.0.0", 49 | "octonode": "git+https://github.com/Thatkookooguy/octonode.git", 50 | "pkginfo": "^0.4.1", 51 | "prompt-checkbox": "^2.2.0", 52 | "prompt-confirm": "^1.2.0", 53 | "q": "^1.5.1", 54 | "sanitize-html": "^1.16.3", 55 | "shelljs": "^0.8.3", 56 | "strip-ansi": "^5.0.0", 57 | "unescape": "^1.0.1", 58 | "wrap-ansi": "^4.0.0", 59 | "write-file-promise": "^1.0.0" 60 | }, 61 | "devDependencies": { 62 | "npm-check": "^5.9.0", 63 | "npm-install-peers": "^1.2.1", 64 | "typescript": "^3.3.3333" 65 | } 66 | } 67 | -------------------------------------------------------------------------------- /lib/gitflow/finish.js: -------------------------------------------------------------------------------- 1 | var NodeGit = require('nodegit-flow')(require('nodegit')); 2 | var gitRoot = require('../kb-git-root'); 3 | 4 | var GLOB = { 5 | config: {}, 6 | repo: {} 7 | }; 8 | 9 | var finishGitflow = {}; 10 | 11 | finishGitflow.finish = finish; 12 | 13 | module.exports = finishGitflow; 14 | 15 | function finish() { 16 | gitRoot.getGitRoot() 17 | .then(function(_gitRoot) { 18 | if (!_gitRoot) { 19 | console.info(kbString.error('git repo not found')); 20 | process.exit(1); 21 | } 22 | 23 | GLOB.gitRoot = _gitRoot; 24 | 25 | return NodeGit.Repository.open(GLOB.gitRoot); 26 | }) 27 | .then(function(repo) { 28 | GLOB.repo = repo; 29 | return NodeGit.Flow.getConfig(repo); 30 | }) 31 | .then(function(config) { 32 | GLOB.config = config; 33 | console.log(config); 34 | return GLOB.repo.getCurrentBranch(); 35 | }) 36 | .then(function(branchRef) { 37 | return branchRef.name(); 38 | }) 39 | .then(function(branchName) { 40 | console.log(branchName); 41 | if (branchName.indexOf(GLOB.config['gitflow.prefix.feature']) >= 0) { 42 | console.log('feature branch'); 43 | var featureName = branchName.replace(/.*\//gi, ''); 44 | return NodeGit.Flow.finishFeature(GLOB.repo, featureName); 45 | } else 46 | if (branchName.indexOf(GLOB.config['gitflow.prefix.hotfix']) >= 0) { 47 | console.log('hotfix branch'); 48 | var hotfixName = branchName.replace(/.*\//gi, ''); 49 | return NodeGit.Flow.finishHotfix(GLOB.repo, hotfixName); 50 | } else 51 | if (branchName.indexOf(GLOB.config['gitflow.prefix.release']) >= 0) { 52 | console.log('release branch'); 53 | var releaseName = branchName.replace(/.*\//gi, ''); 54 | return NodeGit.Flow.finishRelease(GLOB.repo, releaseName); 55 | } else { 56 | console.log('not a branch you can finish!'); 57 | } 58 | process.exit(0); 59 | }) 60 | .then(function(mergeCommit) { 61 | console.log(mergeCommit); // => the sha of the newly created commit 62 | process.exit(0); 63 | }) 64 | .catch(function(error) { 65 | console.error(error); 66 | process.exit(1); 67 | }); 68 | } 69 | -------------------------------------------------------------------------------- /.eslintrc: -------------------------------------------------------------------------------- 1 | { 2 | 'plugins': ['lodash', 'kibibit'], 3 | 'rules': { 4 | 'lodash/prefer-is-nil': 2, 5 | 'lodash/prefer-lodash-typecheck': 2, 6 | 'lodash/prefer-startswith': 2, 7 | 'lodash/prefer-includes': 2, 8 | 'lodash/prefer-noop': 2, 9 | 'kibibit/lodash-typecheck-instead-of-angular': 1, 10 | 'kibibit/angular-modules-functions-order': 1, 11 | 'accessor-pairs': 'error', 12 | 'no-unused-vars': 'error', 13 | 'default-case': 'error', 14 | 'eqeqeq': 'error', 15 | 'no-extend-native': 'error', 16 | 'curly': [ 17 | 2, 18 | 'all' 19 | ], 20 | 'operator-linebreak': [ 21 | 2, 22 | 'after' 23 | ], 24 | 'camelcase': [ 25 | 2, 26 | { 27 | 'properties': 'always' 28 | } 29 | ], 30 | 'max-len': [ 31 | 2, 32 | 80 33 | ], 34 | 'indent': [ 35 | 2, 36 | 2, 37 | { 38 | 'SwitchCase': 1 39 | } 40 | ], 41 | 'quotes': [ 42 | 2, 43 | 'single' 44 | ], 45 | 'no-multi-str': 2, 46 | 'no-mixed-spaces-and-tabs': 2, 47 | 'no-trailing-spaces': 2, 48 | 'space-unary-ops': [ 49 | 2, 50 | { 51 | 'nonwords': false, 52 | 'overrides': {} 53 | } 54 | ], 55 | 'one-var': [ 56 | 2, 57 | { 58 | 'uninitialized': 'always', 59 | 'initialized': 'never' 60 | } 61 | ], 62 | 'brace-style': [ 63 | 2, 64 | '1tbs', 65 | { 66 | 'allowSingleLine': true 67 | } 68 | ], 69 | 'keyword-spacing': [ 70 | 2, 71 | {} 72 | ], 73 | 'space-infix-ops': 2, 74 | 'space-before-blocks': [ 75 | 2, 76 | 'always' 77 | ], 78 | 'eol-last': 2, 79 | 'space-before-function-paren': [ 80 | 2, 81 | { 82 | 'anonymous': 'ignore', 83 | 'named': 'never' 84 | } 85 | ], 86 | 'array-bracket-spacing': [ 87 | 2, 88 | 'never', 89 | { 90 | 'singleValue': true 91 | } 92 | ], 93 | 'space-in-parens': [ 94 | 2, 95 | 'never' 96 | ], 97 | 'no-multiple-empty-lines': 2, 98 | 'no-with': 2, 99 | 'no-spaced-func': 2, 100 | 'key-spacing': [ 101 | 2, 102 | { 103 | 'beforeColon': false, 104 | 'afterColon': true 105 | } 106 | ], 107 | 'dot-notation': 2, 108 | 'semi': [ 109 | 2, 110 | 'always' 111 | ], 112 | 'valid-jsdoc': 2 113 | } 114 | } 115 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 |

2 | 3 |

4 | kibibit-cli (aka bit) 5 |

6 |

7 |

8 | 12 | 13 |

14 |

15 | A git-flow + GitHub replacement for git and git flow 16 |

17 |
18 | 19 | ### installation 20 | run `npm install -g https://github.com/Kibibit/kibibit-cli.git` 21 | `kibibit status` 22 | 23 | This is intended to replace your `git` with `bit` (right now it's `kibibit`). 24 | 25 | `bit` uses the git-flow methodology to optimize team work on GitHub. 26 | 27 | - `git status` -> `bit status` 28 | - `git checkout master` -> `bit master` 29 | - `git checkout develop` -> `bit develop` 30 | 31 | ``` 32 | Usage: kibibit-cli.js [options] [null] 33 | 34 | Commands: 35 | 36 | commit clone a remote repository 37 | status show current branch status 38 | init initialize the gitflow tools for the current repo. (GitHub login, etc.) 39 | clone clone a remote repository 40 | feature start or continue a feature (will be prompted). If no featureName is given, returns all ongoing features 41 | hotfix start or continue a hotfix (will be prompted). If no hotfixName is given, returns all ongoing hotfixes 42 | finish use GitHub to issue a pull request to origin/develop. 43 | release When you have enough completed features in origin/develop, create a release branch, test it and fix it, and then merge it into origin/master 44 | update keep up-to-date with completed features on GitHub 45 | push push your feature branch back to GitHub as you make progress and want to save your work 46 | master checkout master branch 47 | develop checkout develop branch 48 | 49 | Options: 50 | 51 | -h, --help output help information 52 | -v, --version output version information 53 | ``` 54 | -------------------------------------------------------------------------------- /lib/services/kb-user.js: -------------------------------------------------------------------------------- 1 | var KbError = require('../errors/KbError'); 2 | var globalBitConfig = require('home-config').load('.kibibit-bit'); 3 | var keytar = require('keytar'); 4 | var _ = require('lodash'); 5 | var Q = require('q'); 6 | var inquirer = require('inquirer'); 7 | var kbGithub = require('../services/kb-github'); 8 | 9 | var kbUser = {}; 10 | 11 | kbUser.getExistingBitUsers = getExistingBitUsers; 12 | kbUser.getBitUserToken = getBitUserToken; 13 | kbUser.addBitUser = addBitUser; 14 | 15 | kbUser.questions = {}; 16 | kbUser.questions.selectOrCreateUser = selectOrCreateUser; 17 | kbUser.questions.loginNewUser = loginNewUser; 18 | 19 | initBitUsersArray(); 20 | 21 | module.exports = kbUser; 22 | 23 | function initBitUsersArray() { 24 | if (!_.isArray(globalBitConfig.users)) { 25 | globalBitConfig.users = []; 26 | 27 | globalBitConfig.save(); 28 | } 29 | } 30 | 31 | function loginNewUser() { 32 | return kbGithub.questions.userQuestionsGithubLogin() 33 | .then(function(newUser) { 34 | console.log('user logged in', newUser); 35 | 36 | return addBitUser(newUser.username, newUser.token); 37 | }); 38 | } 39 | 40 | function selectOrCreateUser() { 41 | if (!globalBitConfig.users.length) { 42 | return Q.fcall(function() { 43 | return kbUser.questions.loginNewUser(); 44 | }); 45 | } 46 | 47 | var username, isSelectedExistingUser; 48 | 49 | return inquirer.prompt([ { 50 | type: 'list', 51 | name: 'selectUser', 52 | message: 'Found registered users. Select a user for this repo', 53 | choices: globalBitConfig.users.concat([ 54 | new inquirer.Separator(), 55 | 'Add a new user' 56 | ]) 57 | } ]) 58 | .then(function(answers) { 59 | isSelectedExistingUser = 60 | globalBitConfig.users.indexOf(answers.selectUser) > -1; 61 | console.log('isSelectedExistingUser?', isSelectedExistingUser); 62 | username = answers.selectUser; 63 | return isSelectedExistingUser ? 64 | getBitUserToken(username) : 65 | kbUser.questions.loginNewUser(); 66 | }) 67 | .then(function(data) { 68 | return isSelectedExistingUser ? { 69 | username: username, 70 | token: data 71 | } : data; 72 | }) 73 | .then(function(user) { 74 | return kbGithub.getGitHubUserData(user.username, user.token); 75 | }); 76 | } 77 | 78 | function getExistingBitUsers() { 79 | return Q.fcall(function() { 80 | return globalBitConfig.users; 81 | }); 82 | } 83 | 84 | function getBitUserToken(username) { 85 | return keytar.getPassword('kibibit-cli', username); 86 | } 87 | 88 | function addBitUser(gitHubUsername, token) { 89 | if (globalBitConfig.users.indexOf(gitHubUsername) === -1) { 90 | globalBitConfig.users.push(gitHubUsername); 91 | } 92 | 93 | return keytar.setPassword('kibibit-cli', gitHubUsername, token) 94 | .then(function() { 95 | return globalBitConfig.save(); 96 | }) 97 | .then(function() { 98 | return gitHubUsername; 99 | }) 100 | .catch(function() { 101 | throw new KbError('something went wrong', 1); 102 | }); 103 | } 104 | -------------------------------------------------------------------------------- /lib/gitflow/sync.js: -------------------------------------------------------------------------------- 1 | var GLOBAL = {}; 2 | var NodeGit = require('nodegit-flow')(require('nodegit')); 3 | var gitRoot = require('../kb-git-root'); 4 | var keytar = require('keytar'); 5 | // var kbString = require('../kb-string'); 6 | 7 | var syncGitflow = {}; 8 | 9 | syncGitflow.sync = sync; 10 | 11 | module.exports = syncGitflow; 12 | 13 | var cloneOptions = { 14 | fetchOpts: { 15 | callbacks: { 16 | certificateCheck: function() { 17 | return 1; 18 | }, 19 | credentials: function() { 20 | console.log('asked for credentials', GLOBAL.token); 21 | return NodeGit.Cred.userpassPlaintextNew(GLOBAL.token, 'x-oauth-basic'); 22 | } 23 | } 24 | } 25 | }; 26 | 27 | function sync() { 28 | console.log('get started!'); 29 | gitRoot.getGitRoot() 30 | .then(function(_gitRoot) { 31 | console.log('gitRoot passed'); 32 | if (!_gitRoot) { 33 | console.info('git repo not found'); 34 | process.exit(1); 35 | } else { 36 | GLOBAL.gitRoot = _gitRoot; 37 | // open the git repo if it exists 38 | console.log('opened repo'); 39 | return NodeGit.Repository.open(GLOBAL.gitRoot); 40 | } 41 | }) 42 | .then(function(repo) { 43 | GLOBAL.repo = repo; 44 | console.log('found repo. trying to fetch'); 45 | return NodeGit.Remote.list(GLOBAL.repo); 46 | }) 47 | .then(function(remoteList) { 48 | console.log('here are all the remotes:', remoteList); 49 | 50 | if (!remoteList.length) { 51 | console.log('no remotes found. need to add one to update'); 52 | process.exit(1); 53 | } 54 | 55 | return GLOBAL.repo.config() 56 | .then(function(config) { 57 | return config.getString('kibibit.user'); 58 | }); 59 | // process.exit(0); 60 | 61 | // return GLOBAL.repo.fetch('origin', cloneOptions.fetchOpts); 62 | }) 63 | .then(function(userToUse) { 64 | GLOBAL.username = userToUse; 65 | console.log('user to use', GLOBAL.username); 66 | return keytar.getPassword('kibibit-cli', GLOBAL.username); 67 | }) 68 | .then(function(token) { 69 | GLOBAL.token = token; 70 | 71 | console.log('your token', token); 72 | return GLOBAL.repo.fetchAll(cloneOptions.fetchOpts); 73 | process.exit(1); 74 | }) 75 | .then(function() { 76 | return NodeGit.Flow.getConfig(GLOBAL.repo); 77 | }) 78 | .then(function(config) { 79 | GLOBAL.gitflowConfig = config; 80 | return GLOBAL.repo.mergeBranches( 81 | GLOBAL.gitflowConfig['gitflow.branch.master'], 82 | 'origin/' + GLOBAL.gitflowConfig['gitflow.branch.master']); 83 | }) 84 | .then(function() { 85 | return GLOBAL.repo.mergeBranches( 86 | GLOBAL.gitflowConfig['gitflow.branch.develop'], 87 | 'origin/' + GLOBAL.gitflowConfig['gitflow.branch.develop']); 88 | }) 89 | // TODO(thatkookooguy): also update current branch! 90 | // .done(function() { 91 | // console.log('all done!'); 92 | // process.exit(0); 93 | // }) 94 | .then(function(result) { 95 | console.log('the result?', result); 96 | process.exit(0); 97 | }) 98 | .catch(function(error) { 99 | console.trace(error); 100 | process.exit(1); 101 | }); 102 | } 103 | -------------------------------------------------------------------------------- /lib/kb-string.js: -------------------------------------------------------------------------------- 1 | var kbString = {}; 2 | 3 | var clc = require('cli-color'); 4 | var _ = require('lodash'); 5 | 6 | var msgColors = clc.xterm(45).bgXterm(236); 7 | var headerColors = clc.bgXterm(0); 8 | var warningColors = clc.xterm(184); 9 | var errorColors = clc.xterm(196); 10 | var infoColors = clc.xterm(39); 11 | var successColors = clc.xterm(42); 12 | var whiteColors = clc.xterm(15); 13 | var ohNoColors = clc.bgXterm(9).xterm(15); 14 | 15 | kbString.important = buildImportant; 16 | kbString.msg = buildMsg; 17 | kbString.header = buildHeader; 18 | kbString.warning = buildWarning; 19 | kbString.error = buildError; 20 | kbString.info = buildInfo; 21 | kbString.success = buildSuccess; 22 | kbString.white = buildWhite; 23 | kbString.ohNo = buildOhNo; 24 | kbString.param = buildParam; 25 | kbString.kibibitLogo = kibibitLogo; 26 | kbString.build = build; 27 | kbString.printFlash = printFlash; 28 | 29 | module.exports = kbString; 30 | 31 | function buildImportant() { 32 | return clc.bold.underline.blink(kbString.build.apply(this, arguments)); 33 | } 34 | 35 | function buildParam() { 36 | return clc.cyan(kbString.build.apply(this, arguments)); 37 | } 38 | 39 | function buildMsg() { 40 | return msgColors(kbString.build.apply(this, arguments)); 41 | } 42 | 43 | function buildHeader() { 44 | return headerColors(kbString.build.apply(this, arguments)); 45 | } 46 | 47 | function buildWarning() { 48 | return warningColors(kbString.build.apply(this, arguments)); 49 | } 50 | 51 | function buildError() { 52 | return errorColors(kbString.build.apply(this, arguments)); 53 | } 54 | 55 | function buildInfo() { 56 | return infoColors(kbString.build.apply(this, arguments)); 57 | } 58 | 59 | function buildSuccess() { 60 | return successColors(kbString.build.apply(this, arguments)); 61 | } 62 | 63 | function buildWhite() { 64 | return whiteColors(kbString.build.apply(this, arguments)); 65 | } 66 | 67 | function buildOhNo() { 68 | return ohNoColors(kbString.build.apply(this, arguments)); 69 | } 70 | 71 | function printFlash() { 72 | console.log(kbString.kibibitLogo(true)); 73 | console.log(kbString.success(' gitflow + github cli ')); 74 | console.log('======================'); 75 | console.log(''); 76 | 77 | return; 78 | } 79 | 80 | function kibibitLogo(big) { 81 | if (big) { 82 | return kbString.build([ 83 | ' _ ', kbString.error('_'), ' _ ', kbString.info('_'), 84 | ' _ ', kbString.warning('_'), ' _ \n', 85 | '| | _', kbString.error('(_)'), ' |__ ', kbString.info('(_)'), 86 | ' |__ ', kbString.warning('(_)'), ' |_ \n', 87 | '| |/ / ', kbString.error('|'), ' \'_ \\', kbString.info('| |'), 88 | ' \'_ \\', kbString.warning('| |'), ' __|\n', 89 | '| <', kbString.error('| |'), ' |_) ', kbString.info('| |'), 90 | ' |_) ', kbString.warning('| |'), ' |_ \n', 91 | '|_|\\_\\', kbString.error('_|'), '_.__/', kbString.info('|_|'), 92 | '_.__/', kbString.warning('|_|'), '\\__|\n' 93 | ]); 94 | } 95 | return kbString.build([ 96 | kbString.white('k'), 97 | kbString.error('i'), 98 | kbString.white('b'), 99 | kbString.info('i'), 100 | kbString.white('b'), 101 | kbString.warning('i'), 102 | kbString.white('t') 103 | ]); 104 | } 105 | 106 | function build() { 107 | var array = _.isArray(arguments[0]) ? arguments[0] : arguments; 108 | 109 | return _.join(array, ''); 110 | } 111 | -------------------------------------------------------------------------------- /lib/gitflow/hotfix.js: -------------------------------------------------------------------------------- 1 | var kbString = require('../kb-string'); 2 | var _ = require('lodash'); 3 | var Q = require('q'); 4 | var NodeGit = require('nodegit-flow')(require('nodegit')); 5 | var moment = require('moment'); 6 | var gitRoot = require('../kb-git-root'); 7 | 8 | var GLOBAL = { 9 | repo: null, 10 | featureBranches: null 11 | }; 12 | 13 | var hotfixGitflow = {}; 14 | 15 | hotfixGitflow.hotfix = hotfix; 16 | 17 | module.exports = hotfixGitflow; 18 | 19 | function startHotfix(args) { 20 | return NodeGit.Flow.startHotfix( 21 | GLOBAL.repo, 22 | args.hotfixName 23 | ) 24 | .then(function(branch) { 25 | console 26 | .log('creating hotfix named', args.hotfixName); 27 | return branch; 28 | }) 29 | .catch(function(/*error*/) { 30 | // console.trace(error); 31 | return NodeGit.Flow.getConfig(GLOBAL.repo) 32 | .then(function(config) { 33 | // console.log(config); 34 | GLOBAL.currHotfixBranch = 35 | config['gitflow.prefix.hotfix'] + args.hotfixName; 36 | console 37 | .log('checking out existing hotfix ', GLOBAL.currHotfixBranch); 38 | return GLOBAL.repo.checkoutBranch(GLOBAL.currHotfixBranch); 39 | // process.exit(1); 40 | }) 41 | .then(function() { 42 | return GLOBAL.repo.getBranch(GLOBAL.currHotfixBranch); 43 | }); 44 | }); 45 | } 46 | 47 | function hotfix(args, options) { 48 | 49 | gitRoot.getGitRoot() 50 | .then(function(_gitRoot) { 51 | if (!_gitRoot) { 52 | console.info(kbString.error('git repo not found')); 53 | process.exit(1); 54 | } else { 55 | GLOBAL.gitRoot = _gitRoot; 56 | // open the git repo if it exists 57 | return NodeGit.Repository.open(GLOBAL.gitRoot); 58 | } 59 | }) 60 | .then(function(repo) { 61 | GLOBAL.repo = repo; 62 | 63 | if (!args.hotfixName) { 64 | return GLOBAL.repo.getReferenceNames(NodeGit.Reference.TYPE.LISTALL); 65 | } else { 66 | return startHotfix(args) 67 | .then(function(/* featureBranch */) { 68 | // upload branch to github 69 | // (either empty or with an empty init commit) 70 | // console.log(featureBranch.shorthand()); // => feautre/my-feature 71 | process.exit(0); 72 | }); 73 | } 74 | }) 75 | .then(function(allBranches) { 76 | var regexItem = options.remote ? 77 | /^refs\/remotes\/.*?\/hotfix\// : 78 | /^refs\/heads\/hotfix\//; 79 | 80 | allBranches = _.filter(allBranches, function(branch) { 81 | return regexItem.test(branch); // feature\/ 82 | }); 83 | 84 | var hotfixBranches = _.map(allBranches, function(branch) { 85 | return branch.replace(regexItem, ''); 86 | }); 87 | 88 | GLOBAL.hotfixBranches = hotfixBranches; 89 | 90 | console.log(kbString.success([ 91 | 'Found ', 92 | hotfixBranches.length, ' ', 93 | kbString.important(options.remote ? 'remote' : 'local'), 94 | ' hotfixes:\n', 95 | '======================' 96 | ])); 97 | 98 | // todo(thatkookooguy): simplify this! 99 | return Q.all(_.map(allBranches, function(branch) { 100 | return GLOBAL.repo.getBranchCommit(branch) 101 | .then(function(result) { 102 | return result; 103 | }, function(error) { 104 | var deferred = Q.defer(); 105 | deferred.resolve(error); 106 | 107 | return deferred.promise; 108 | }); 109 | })); 110 | }) 111 | .then(function(allLastCommits) { 112 | _.forEach(allLastCommits, function(commit, index) { 113 | if (commit.errno) { 114 | console.log(kbString.build([ 115 | kbString.info([ 116 | '[', 117 | 'hotfix/', 118 | GLOBAL.hotfixBranches[index], 119 | ']' 120 | ]), 121 | ' ', 122 | kbString.error('no commits yet...'), 123 | ' ---ERROR: ', commit.message 124 | ])); 125 | return; 126 | } 127 | console.log(kbString.build([ 128 | kbString.info('[', 'hotfix/', GLOBAL.hotfixBranches[index], ']'), 129 | ' ', 130 | kbString.white('(', commit.author(), ')'), 131 | kbString.error(' > '), 132 | kbString.success(commit.message().trim().split('\n', 1)[0]), 133 | kbString.warning(' (', moment(commit.date()).fromNow(), ')') 134 | ])); 135 | }); 136 | process.exit(0); 137 | }) 138 | .catch(function(error) { 139 | console.trace('oops.... something went wrong...', error); 140 | process.exit(1); 141 | }); 142 | } 143 | -------------------------------------------------------------------------------- /lib/gitflow/feature.js: -------------------------------------------------------------------------------- 1 | var kbString = require('../kb-string'); 2 | var _ = require('lodash'); 3 | var Q = require('q'); 4 | var NodeGit = require('nodegit-flow')(require('nodegit')); 5 | var moment = require('moment'); 6 | var gitRoot = require('../kb-git-root'); 7 | 8 | var GLOBAL = { 9 | repo: null, 10 | featureBranches: null 11 | }; 12 | 13 | var featureGitflow = {}; 14 | 15 | featureGitflow.feature = feature; 16 | 17 | module.exports = featureGitflow; 18 | 19 | function startFeature(args) { 20 | return NodeGit.Flow.startFeature( 21 | GLOBAL.repo, 22 | args.featureName 23 | ) 24 | .then(function(branch) { 25 | console 26 | .log('creating feature named', args.featureName); 27 | return branch; 28 | }) 29 | .catch(function(/*error*/) { 30 | // console.trace(error); 31 | return NodeGit.Flow.getConfig(GLOBAL.repo) 32 | .then(function(config) { 33 | // console.log(config); 34 | GLOBAL.currFeatureBranch = 35 | config['gitflow.prefix.feature'] + args.featureName; 36 | console 37 | .log('checking out existing feature ', GLOBAL.currFeatureBranch); 38 | return GLOBAL.repo.checkoutBranch(GLOBAL.currFeatureBranch); 39 | // process.exit(1); 40 | }) 41 | .then(function() { 42 | return GLOBAL.repo.getBranch(GLOBAL.currFeatureBranch); 43 | }); 44 | }); 45 | } 46 | 47 | function feature(args, options) { 48 | 49 | gitRoot.getGitRoot() 50 | .then(function(_gitRoot) { 51 | if (!_gitRoot) { 52 | console.info(kbString.error('git repo not found')); 53 | process.exit(1); 54 | } else { 55 | GLOBAL.gitRoot = _gitRoot; 56 | // open the git repo if it exists 57 | return NodeGit.Repository.open(GLOBAL.gitRoot); 58 | } 59 | }) 60 | .then(function(repo) { 61 | GLOBAL.repo = repo; 62 | 63 | if (!args.featureName) { 64 | return GLOBAL.repo.getReferenceNames(NodeGit.Reference.TYPE.LISTALL); 65 | } else { 66 | return startFeature(args) 67 | .then(function(/* featureBranch */) { 68 | // upload branch to github 69 | // (either empty or with an empty init commit) 70 | // console.log(featureBranch.shorthand()); // => feautre/my-feature 71 | process.exit(0); 72 | }); 73 | } 74 | }) 75 | .then(function(allBranches) { 76 | var regexItem = options.remote ? 77 | /^refs\/remotes\/.*?\/feature\// : 78 | /^refs\/heads\/feature\//; 79 | 80 | allBranches = _.filter(allBranches, function(branch) { 81 | return regexItem.test(branch); // feature\/ 82 | }); 83 | 84 | var featureBranches = _.map(allBranches, function(branch) { 85 | return branch.replace(regexItem, ''); 86 | }); 87 | 88 | GLOBAL.featureBranches = featureBranches; 89 | 90 | console.log(kbString.success([ 91 | 'Found ', 92 | featureBranches.length, ' ', 93 | kbString.important(options.remote ? 'remote' : 'local'), 94 | ' features:\n', 95 | '======================' 96 | ])); 97 | 98 | // todo(thatkookooguy): simplify this! 99 | return Q.all(_.map(allBranches, function(branch) { 100 | return GLOBAL.repo.getBranchCommit(branch) 101 | .then(function(result) { 102 | return result; 103 | }, function(error) { 104 | var deferred = Q.defer(); 105 | deferred.resolve(error); 106 | 107 | return deferred.promise; 108 | }); 109 | })); 110 | }) 111 | .then(function(allLastCommits) { 112 | _.forEach(allLastCommits, function(commit, index) { 113 | if (commit.errno) { 114 | console.log(kbString.build([ 115 | kbString.info([ 116 | '[', 117 | 'feature/', 118 | GLOBAL.featureBranches[index], 119 | ']' 120 | ]), 121 | ' ', 122 | kbString.error('no commits yet...'), 123 | ' ---ERROR: ', commit.message 124 | ])); 125 | return; 126 | } 127 | console.log(kbString.build([ 128 | kbString.info('[', 'feature/', GLOBAL.featureBranches[index], ']'), 129 | ' ', 130 | kbString.white('(', commit.author(), ')'), 131 | kbString.error(' > '), 132 | kbString.success(commit.message().trim().split('\n', 1)[0]), 133 | kbString.warning(' (', moment(commit.date()).fromNow(), ')') 134 | ])); 135 | }); 136 | process.exit(0); 137 | }) 138 | .catch(function(error) { 139 | console.trace('oops.... something went wrong...', error); 140 | process.exit(1); 141 | }); 142 | } 143 | -------------------------------------------------------------------------------- /lib/gitflow/utility.js: -------------------------------------------------------------------------------- 1 | var kbString = require('../kb-string'); 2 | var _ = require('lodash'); 3 | var Q = require('q'); 4 | var colorize = require('json-colorizer'); 5 | var github = require('octonode'); 6 | 7 | var util = {}; 8 | 9 | util.addFilesToIndex = addFilesToIndex; 10 | util.openIndex = openIndex; 11 | util.writeFilesInIndex = writeFilesInIndex; 12 | util.writeIndexTree = writeIndexTree; 13 | util.getRootCommit = getRootCommit; 14 | util.getGitHubUserData = getGitHubUserData; 15 | util.getOrgs = getOrgs; 16 | util.createRepoUser = createRepoUser; 17 | util.createRepoOrg = createRepoOrg; 18 | util.signingInAnimation = [ 19 | kbString.info(kbString.warning('/'), ' Signing in'), 20 | kbString.info(kbString.warning('|'), ' Signing in..'), 21 | kbString.info(kbString.warning('\\'), ' Signing in..'), 22 | kbString.info(kbString.warning('-'), ' Signing in...') 23 | ]; 24 | util.signingInSteps = 4; 25 | 26 | module.exports = util; 27 | 28 | var client; 29 | 30 | function createRepoUser(name, description) { 31 | var deferred = Q.defer(); 32 | 33 | description = description || ''; 34 | 35 | if (!name) { 36 | deferred.reject('name must be given'); 37 | 38 | return deferred.promise; 39 | } 40 | 41 | var ghme = client.me(); 42 | 43 | ghme.repo({ 44 | 'name': name, 45 | 'description': description, 46 | }, function(err, body) { 47 | if (err) { 48 | // console.log('ERROR!', err); 49 | 50 | deferred.reject('can\'t create user repo'); 51 | } else { 52 | deferred.resolve(body); 53 | } 54 | }); //repo 55 | 56 | return deferred.promise; 57 | } 58 | 59 | function createRepoOrg(orgName, name, description) { 60 | var deferred = Q.defer(); 61 | 62 | description = description || ''; 63 | 64 | if (!name || !orgName) { 65 | deferred.reject('orgName & name must be given'); 66 | 67 | return deferred.promise; 68 | } 69 | 70 | var ghorg = client.org(orgName); 71 | 72 | ghorg.repo({ 73 | name: name, 74 | description: description 75 | }, function(err, body) { 76 | if (err) { 77 | // console.log('ERROR!', err); 78 | deferred.reject('can\'t create organization repo') 79 | } else { 80 | deferred.resolve(body); 81 | } 82 | }); 83 | 84 | return deferred.promise; 85 | } 86 | 87 | function getOrgs() { 88 | var deferred = Q.defer(); 89 | 90 | var ghme = client.me(); 91 | 92 | ghme.orgs(function(err, body) { 93 | if (err) { 94 | // console.log('ERROR!', err); 95 | deferred.reject('can\'t get organizations data from github'); 96 | } else { 97 | deferred.resolve(body); 98 | } 99 | }); 100 | 101 | return deferred.promise; 102 | } 103 | 104 | function getGitHubUserData(username, token) { 105 | console.log('trying to login to github', token); 106 | var deferred = Q.defer(); 107 | 108 | client = github.client(token); 109 | 110 | client.get('/user', {}, function (err, status, body) { 111 | if (err) { 112 | // console.log('ERROR!', err); 113 | deferred.reject('can\'t get user data from github'); 114 | } else { 115 | // console.log(body); //json object 116 | deferred.resolve(body); 117 | } 118 | }); 119 | 120 | return deferred.promise; 121 | } 122 | 123 | function addFilesToIndex(index, filesToCommit) { 124 | var fName = arguments.callee.toString().match(/function ([^\(]+)/)[1]; 125 | if (!index) { return Q.reject(fName + ' expects an index arg'); } 126 | // add files 127 | return Q 128 | .allSettled(_.map(filesToCommit, function(file) { 129 | return index.addByPath(file); 130 | })); 131 | } 132 | 133 | // Load up the repository index 134 | function openIndex(repository) { 135 | var fName = arguments.callee.toString().match(/function ([^\(]+)/)[1]; 136 | if (!repository) { return Q.reject(fName + ' expects a repository arg'); } 137 | 138 | return repository.refreshIndex(); 139 | } 140 | 141 | // this will write all files to the index 142 | function writeFilesInIndex(index) { 143 | var fName = arguments.callee.toString().match(/function ([^\(]+)/)[1]; 144 | if (!index) { return Q.reject(fName + ' expects an index arg'); } 145 | if (!index.write) { report(index); } 146 | 147 | return index.write(); 148 | } 149 | 150 | function writeIndexTree(index) { 151 | var fName = arguments.callee.toString().match(/function ([^\(]+)/)[1]; 152 | if (!index) { return Q.reject(fName + ' expects an index arg'); } 153 | 154 | return index.writeTree(); 155 | } 156 | 157 | function getRootCommit(repo, head) { 158 | var fName = arguments.callee.toString().match(/function ([^\(]+)/)[1]; 159 | 160 | if (!repo || !head) { 161 | return Q.reject(fName + ' expects a repo and an index arg'); 162 | } 163 | 164 | return repo.getCommit(head); 165 | } 166 | 167 | function report(what, vars) { 168 | console.error(kbString.error(what), 169 | vars ? colorize(JSON.stringify(vars, null, 2)) : ''); 170 | } 171 | -------------------------------------------------------------------------------- /lib/gitflow/release.js: -------------------------------------------------------------------------------- 1 | var kbString = require('../kb-string'); 2 | var _ = require('lodash'); 3 | var Q = require('q'); 4 | var NodeGit = require('nodegit-flow')(require('nodegit')); 5 | var moment = require('moment'); 6 | var gitRoot = require('../kb-git-root'); 7 | 8 | var GLOBAL = { 9 | repo: null, 10 | releaseBranches: null 11 | }; 12 | 13 | var releaseGitflow = {}; 14 | 15 | releaseGitflow.release = release; 16 | 17 | module.exports = releaseGitflow; 18 | 19 | function startRelease(args) { 20 | return NodeGit.Flow.startRelease( 21 | GLOBAL.repo, 22 | args.releaseName 23 | ) 24 | .then(function(branch) { 25 | console 26 | .log('creating release named', args.releaseName); 27 | return branch; 28 | }) 29 | .catch(function(/*error*/) { 30 | // console.trace(error); 31 | return NodeGit.Flow.getConfig(GLOBAL.repo) 32 | .then(function(config) { 33 | // console.log(config); 34 | GLOBAL.currReleaseBranch = 35 | config['gitflow.prefix.release'] + args.releaseName; 36 | console 37 | .log('checking out existing release ', GLOBAL.currReleaseBranch); 38 | return GLOBAL.repo.checkoutBranch(GLOBAL.currReleaseBranch); 39 | // process.exit(1); 40 | }) 41 | .then(function() { 42 | return GLOBAL.repo.getBranch(GLOBAL.currReleaseBranch); 43 | }); 44 | }); 45 | } 46 | 47 | function release(args, options) { 48 | 49 | gitRoot.getGitRoot() 50 | .then(function(_gitRoot) { 51 | if (!_gitRoot) { 52 | console.info(kbString.error('git repo not found')); 53 | process.exit(1); 54 | } else { 55 | GLOBAL.gitRoot = _gitRoot; 56 | // open the git repo if it exists 57 | return NodeGit.Repository.open(GLOBAL.gitRoot); 58 | } 59 | }) 60 | .then(function(repo) { 61 | GLOBAL.repo = repo; 62 | 63 | return NodeGit.Tag.list(GLOBAL.repo).then(function(array) { 64 | console.log('all tags!', array); 65 | var releaseTags = _.filter(array, function(tag) { 66 | return /^v\d+\.\d+\.\d+$/gi.test(tag); 67 | }); 68 | console.log('only release tags', releaseTags); 69 | process.exit(1); 70 | return NodeGit.Tag.lookupPrefix(GLOBAL.repo, 'v', 1); 71 | }); 72 | }) 73 | .then(function() { 74 | 75 | if (!args.releaseName) { 76 | return GLOBAL.repo.getReferenceNames(NodeGit.Reference.TYPE.LISTALL); 77 | } else { 78 | return startRelease(args) 79 | .then(function(/* releaseBranch */) { 80 | // upload branch to github 81 | // (either empty or with an empty init commit) 82 | // console.log(releaseBranch.shorthand()); // => release/my-release 83 | process.exit(0); 84 | }); 85 | } 86 | }) 87 | .then(function(allBranches) { 88 | var regexItem = options.remote ? 89 | /^refs\/remotes\/.*?\/release\// : 90 | /^refs\/heads\/release\//; 91 | 92 | allBranches = _.filter(allBranches, function(branch) { 93 | return regexItem.test(branch); // release\/ 94 | }); 95 | 96 | var releaseBranches = _.map(allBranches, function(branch) { 97 | return branch.replace(regexItem, ''); 98 | }); 99 | 100 | GLOBAL.releaseBranches = releaseBranches; 101 | 102 | console.log(kbString.success([ 103 | 'Found ', 104 | releaseBranches.length, ' ', 105 | kbString.important(options.remote ? 'remote' : 'local'), 106 | ' releases:\n', 107 | '======================' 108 | ])); 109 | 110 | // todo(thatkookooguy): simplify this! 111 | return Q.all(_.map(allBranches, function(branch) { 112 | return GLOBAL.repo.getBranchCommit(branch) 113 | .then(function(result) { 114 | return result; 115 | }, function(error) { 116 | var deferred = Q.defer(); 117 | deferred.resolve(error); 118 | 119 | return deferred.promise; 120 | }); 121 | })); 122 | }) 123 | .then(function(allLastCommits) { 124 | _.forEach(allLastCommits, function(commit, index) { 125 | if (commit.errno) { 126 | console.log(kbString.build([ 127 | kbString.info([ 128 | '[', 129 | 'release/', 130 | GLOBAL.releaseBranches[index], 131 | ']' 132 | ]), 133 | ' ', 134 | kbString.error('no commits yet...'), 135 | ' ---ERROR: ', commit.message 136 | ])); 137 | return; 138 | } 139 | console.log(kbString.build([ 140 | kbString.info('[', 'release/', GLOBAL.releaseBranches[index], ']'), 141 | ' ', 142 | kbString.white('(', commit.author(), ')'), 143 | kbString.error(' > '), 144 | kbString.success(commit.message().trim().split('\n', 1)[0]), 145 | kbString.warning(' (', moment(commit.date()).fromNow(), ')') 146 | ])); 147 | }); 148 | process.exit(0); 149 | }) 150 | .catch(function(error) { 151 | console.trace('oops.... something went wrong...', error); 152 | process.exit(1); 153 | }); 154 | } 155 | -------------------------------------------------------------------------------- /tsconfig.json: -------------------------------------------------------------------------------- 1 | { 2 | "compilerOptions": { 3 | /* Basic Options */ 4 | "target": "es5", /* Specify ECMAScript target version: 'ES3' (default), 'ES5', 'ES2015', 'ES2016', 'ES2017','ES2018' or 'ESNEXT'. */ 5 | "module": "commonjs", /* Specify module code generation: 'none', 'commonjs', 'amd', 'system', 'umd', 'es2015', or 'ESNext'. */ 6 | // "lib": [], /* Specify library files to be included in the compilation. */ 7 | // "allowJs": true, /* Allow javascript files to be compiled. */ 8 | // "checkJs": true, /* Report errors in .js files. */ 9 | // "jsx": "preserve", /* Specify JSX code generation: 'preserve', 'react-native', or 'react'. */ 10 | // "declaration": true, /* Generates corresponding '.d.ts' file. */ 11 | // "declarationMap": true, /* Generates a sourcemap for each corresponding '.d.ts' file. */ 12 | "sourceMap": true, /* Generates corresponding '.map' file. */ 13 | // "outFile": "./", /* Concatenate and emit output to single file. */ 14 | "outDir": "./dist", /* Redirect output structure to the directory. */ 15 | "rootDir": "./", /* Specify the root directory of input files. Use to control the output directory structure with --outDir. */ 16 | // "composite": true, /* Enable project compilation */ 17 | // "removeComments": true, /* Do not emit comments to output. */ 18 | // "noEmit": true, /* Do not emit outputs. */ 19 | // "importHelpers": true, /* Import emit helpers from 'tslib'. */ 20 | // "downlevelIteration": true, /* Provide full support for iterables in 'for-of', spread, and destructuring when targeting 'ES5' or 'ES3'. */ 21 | // "isolatedModules": true, /* Transpile each file as a separate module (similar to 'ts.transpileModule'). */ 22 | /* Strict Type-Checking Options */ 23 | "strict": true, /* Enable all strict type-checking options. */ 24 | "noImplicitAny": true, /* Raise error on expressions and declarations with an implied 'any' type. */ 25 | // "strictNullChecks": true, /* Enable strict null checks. */ 26 | // "strictFunctionTypes": true, /* Enable strict checking of function types. */ 27 | // "strictBindCallApply": true, /* Enable strict 'bind', 'call', and 'apply' methods on functions. */ 28 | // "strictPropertyInitialization": true, /* Enable strict checking of property initialization in classes. */ 29 | // "noImplicitThis": true, /* Raise error on 'this' expressions with an implied 'any' type. */ 30 | // "alwaysStrict": true, /* Parse in strict mode and emit "use strict" for each source file. */ 31 | /* Additional Checks */ 32 | // "noUnusedLocals": true, /* Report errors on unused locals. */ 33 | "noUnusedParameters": true, /* Report errors on unused parameters. */ 34 | // "noImplicitReturns": true, /* Report error when not all code paths in function return a value. */ 35 | // "noFallthroughCasesInSwitch": true, /* Report errors for fallthrough cases in switch statement. */ 36 | /* Module Resolution Options */ 37 | // "moduleResolution": "node", /* Specify module resolution strategy: 'node' (Node.js) or 'classic' (TypeScript pre-1.6). */ 38 | // "baseUrl": "./", /* Base directory to resolve non-absolute module names. */ 39 | // "paths": {}, /* A series of entries which re-map imports to lookup locations relative to the 'baseUrl'. */ 40 | // "rootDirs": [], /* List of root folders whose combined content represents the structure of the project at runtime. */ 41 | // "typeRoots": [], /* List of folders to include type definitions from. */ 42 | // "types": [], /* Type declaration files to be included in compilation. */ 43 | // "allowSyntheticDefaultImports": true, /* Allow default imports from modules with no default export. This does not affect code emit, just typechecking. */ 44 | "esModuleInterop": true, /* Enables emit interoperability between CommonJS and ES Modules via creation of namespace objects for all imports. Implies 'allowSyntheticDefaultImports'. */ 45 | // "preserveSymlinks": true, /* Do not resolve the real path of symlinks. */ 46 | /* Source Map Options */ 47 | // "sourceRoot": "", /* Specify the location where debugger should locate TypeScript files instead of source locations. */ 48 | // "mapRoot": "", /* Specify the location where debugger should locate map files instead of generated locations. */ 49 | // "inlineSourceMap": true, /* Emit a single file with source maps instead of having a separate file. */ 50 | // "inlineSources": true, /* Emit the source alongside the sourcemaps within a single file; requires '--inlineSourceMap' or '--sourceMap' to be set. */ 51 | /* Experimental Options */ 52 | "experimentalDecorators": true, /* Enables experimental support for ES7 decorators. */ 53 | // "emitDecoratorMetadata": true, /* Enables experimental support for emitting type metadata for decorators. */ 54 | } 55 | } 56 | -------------------------------------------------------------------------------- /lib/gitflow/commit.js: -------------------------------------------------------------------------------- 1 | /*eslint-env es6*/ 2 | var KbError = require('../errors/KbError'); 3 | var errorHandler = require('../errors/error-handler')('[COMMIT-FLOW]'); 4 | var kbString = require('../kb-string'); 5 | var Prompt = require('prompt-checkbox'); 6 | var _ = require('lodash'); 7 | var kbRepo = require('../services/kb-repo'); 8 | var stripAnsi = require('strip-ansi'); 9 | var Table = require('cli-table'); 10 | var inquirer = require('inquirer'); 11 | 12 | 13 | var commitGitflow = {}; 14 | 15 | commitGitflow.commit = commit; 16 | 17 | commitGitflow.questions = []; 18 | 19 | module.exports = commitGitflow; 20 | 21 | function commit() { 22 | var data = {}; 23 | 24 | return kbRepo.openGit() 25 | .then(function(repo) { 26 | data.repo = repo; 27 | 28 | return kbRepo.getCurrentGitFilesStatus(data.repo, 29 | false /* shouldAddFolderEntries */); 30 | }) 31 | .then(function(statuses) { 32 | 33 | return selectFilesToCommit(statuses); 34 | }) 35 | .then(function(filesToCommit) { 36 | data.filesToCommit = filesToCommit; 37 | 38 | return askForCommitDetails(); 39 | }) 40 | .then(function(commitDetails) { 41 | data.commitDetails = commitDetails; 42 | return kbRepo.commitFilesByPath(data.repo, { 43 | filesToCommit: data.filesToCommit, 44 | commitMessage: commitDetails.commitMessage 45 | }); 46 | }) 47 | .then(function(commitDetails) { 48 | data.commitDetails = commitDetails; 49 | 50 | var commitSummaryTable = new Table({ 51 | chars: { 52 | 'top': '═', 53 | 'top-mid': '╤', 54 | 'top-left': '╔', 55 | 'top-right': '╗', 56 | 'bottom': '═', 57 | 'bottom-mid': '╧', 58 | 'bottom-left': '╚', 59 | 'bottom-right': '╝', 60 | 'left': '║', 61 | 'left-mid': '╟', 62 | 'mid': '─', 63 | 'mid-mid': '┼', 64 | 'right': '║', 65 | 'right-mid': '╢', 66 | 'middle': '│' 67 | } 68 | }); 69 | 70 | commitSummaryTable.push([ 71 | kbString.info('id'), 72 | kbString.white(data.commitDetails.commit) 73 | ]); 74 | 75 | commitSummaryTable.push([ 76 | kbString.info('push to branch'), 77 | kbString.success(data.commitDetails.branch) 78 | ]); 79 | 80 | commitSummaryTable.push([ 81 | kbString.info('author'), 82 | kbString.build([ 83 | kbString.success(data.commitDetails.authorName), 84 | kbString.warning('<', data.commitDetails.authorEmail, '>') 85 | ]) 86 | ]); 87 | 88 | commitSummaryTable.push([ 89 | kbString.info('commit message'), 90 | data.commitDetails.commitMessage 91 | ]); 92 | 93 | commitSummaryTable.push([ 94 | kbString.info('parent commit'), 95 | kbString.white(data.commitDetails.parents[0]) 96 | ]); 97 | 98 | return commitSummaryTable; 99 | }) 100 | .then(function(commitSummaryTable) { 101 | console.log(kbString.success([ 102 | '\n committed succesfully \n', 103 | '=======================' 104 | ])); 105 | 106 | console.log(commitSummaryTable.toString()); 107 | 108 | process.exit(0); 109 | }) 110 | .catch(errorHandler); 111 | } 112 | 113 | function selectFilesToCommit(statuses) { 114 | var stagedChoices = _.map(statuses.staged, function(status) { 115 | return { 116 | name: kbString.warning(status.path) + ' ' + status.statusStr, 117 | checked: true, 118 | folder: status.folder + '/', 119 | value: kbString.warning(status.path) + ' ' + status.statusStr 120 | }; 121 | }); 122 | 123 | var unstagedChoices = _.map(statuses.unstaged, function(status) { 124 | return { 125 | name: kbString.warning(status.path) + ' ' + status.statusStr, 126 | checked: false, 127 | folder: status.folder + '/', 128 | value: kbString.warning(status.path) + ' ' + status.statusStr 129 | }; 130 | }); 131 | 132 | var allOptions = 133 | _.groupBy(stagedChoices.concat(unstagedChoices), 'folder'); 134 | 135 | if (_.isEmpty(allOptions)) { 136 | console.log(kbString.error([ 137 | 'no changes were found.\n', 138 | 'change some files in order to commit them.' 139 | ])); 140 | process.exit(126); 141 | } 142 | 143 | var prompt = new Prompt({ 144 | name: 'commitFiles', 145 | message: kbString.info( 146 | '==================================================', 147 | kbString.param(' ?\n? '), 148 | 'Which files do you want to include in this commit?', 149 | kbString.param(' ?\n? '), 150 | '==================================================', 151 | kbString.param(' ?\n'), 152 | kbString.warning( 153 | '\n- select files with ', kbString.important('space bar'), 154 | ', confirm selection with ', kbString.important('enter'), ' -\n' 155 | ) 156 | ), 157 | radio: true, 158 | choices: allOptions 159 | }); 160 | 161 | return prompt.run() 162 | .then(function(answers) { 163 | var filesToCommit = _.map(answers, function(answer) { 164 | return stripAnsi(answer).replace(/\s\[.*?\]/, ''); 165 | }); 166 | // console.log(colorize(JSON.stringify(filesToCommit, null, 2))); 167 | 168 | if (!filesToCommit.length) { 169 | throw new KbError('no files were selected', 0); 170 | } 171 | 172 | return filesToCommit; 173 | }); 174 | } 175 | 176 | function askForCommitDetails(branch) { 177 | return inquirer.prompt([ 178 | askForCommitTitle(branch), 179 | editCommitMessageQuestion() 180 | ]) 181 | .then(function(answers) { 182 | var markedMessage; 183 | var completeMessage = [ 184 | answers.commitTitle, 185 | '\n\n', 186 | answers.commitMessage 187 | ].join(''); 188 | 189 | try { 190 | markedMessage = [ 191 | '# ', 192 | answers.commitTitle, 193 | '\n\n', 194 | marked(answers.commitMessage, { 195 | renderer: renderer 196 | }) 197 | ].join(''); 198 | } catch (e) { 199 | console.error('MARKDOWN ERROR!', e); 200 | } 201 | 202 | return { 203 | commitMessage: completeMessage, 204 | markedCommitMessage: markedMessage 205 | }; 206 | }); 207 | } 208 | 209 | function editCommitMessageQuestion() { 210 | return { 211 | type: 'editor', 212 | name: 'commitMessage', 213 | message: 'Please write a short commit message', 214 | validate: function(text) { 215 | if (text && text.split('\n')[0].length > 72) { 216 | return kbString.build([ 217 | 'each line in the commit message must be ', 218 | 'no more than 72 characters long.\n', 219 | 'For more info: ', 220 | 'http://tbaggery.com/2008/04/19/', 221 | 'a-note-about-git-commit-messages.html\n\n' 222 | ]); 223 | } 224 | 225 | return true; 226 | }, 227 | default: function() { 228 | return 'master'; 229 | } 230 | }; 231 | } 232 | 233 | function askForCommitTitle(branch) { 234 | return { 235 | type: 'input', 236 | name: 'commitTitle', 237 | message: 'Please enter a title for your commit - ' + 238 | kbString.param('[', branch || '', ']: '), 239 | validate: function(commitTitle) { 240 | var isTitleShortEnough = commitTitle.length <= 50; 241 | if (!_.isEmpty(commitTitle) && isTitleShortEnough) { 242 | return true; 243 | } 244 | 245 | if (!isTitleShortEnough) { 246 | return kbString.build([ 247 | 'The summary of your commit message must ', 248 | 'be maximum 50 characters long.\n', 249 | 'For more info: ', 250 | 'http://tbaggery.com/2008/04/19/', 251 | 'a-note-about-git-commit-messages.html\n\n' 252 | ]); 253 | } 254 | 255 | return 'no input detected. please try again'; 256 | } 257 | }; 258 | } 259 | -------------------------------------------------------------------------------- /lib/services/kb-github.js: -------------------------------------------------------------------------------- 1 | var github = require('octonode'); 2 | var _ = require('lodash'); 3 | var Q = require('q'); 4 | var inquirer = require('inquirer'); 5 | var os = require('os'); 6 | var kbString = require('../kb-string'); 7 | 8 | var kbGithub = {}; 9 | 10 | kbGithub.loginGitHub = loginGitHub; 11 | kbGithub.getGitHubUserData = getGitHubUserData; 12 | kbGithub.getGitHubBasicUserData = getGitHubBasicUserData; 13 | kbGithub.getGitHubUserOrgs = getGitHubUserOrgs; 14 | kbGithub.createRepoOrg = createRepoOrg; 15 | kbGithub.createRepoUser = createRepoUser; 16 | 17 | kbGithub.questions = {}; 18 | kbGithub.questions.askForGitHubUser = askForGitHubUser; 19 | kbGithub.questions.askForGitHubPassword = askForGitHubPassword; 20 | kbGithub.questions.askForOTPCode = askForOTPCode; 21 | kbGithub.questions.userQuestionsGithubLogin = userQuestionsGithubLogin; 22 | 23 | module.exports = kbGithub; 24 | 25 | function askForGitHubUser() { 26 | return { 27 | type: 'input', 28 | name: 'GitHubUsername', 29 | message: 'GitHub username:', 30 | validate: function(currUsername) { 31 | if (!_.isEmpty(currUsername)) { 32 | return true; 33 | } 34 | 35 | return 'no input detected. please try again'; 36 | } 37 | }; 38 | } 39 | 40 | function askForGitHubPassword(data) { 41 | return { 42 | type: 'password', 43 | name: 'GitHubPassword', 44 | message: 'GitHub password:', 45 | validate: function(currPassword, userAnswers) { 46 | var done = this.async(); 47 | 48 | if (!_.isEmpty(currPassword)) { 49 | kbGithub.loginGitHub(userAnswers.GitHubUsername, currPassword) 50 | .then(function(loginObj) { 51 | data.shouldAskForOTPCode = loginObj.needOTP; 52 | data.OTPMethod = loginObj.OTPMethod; 53 | data.token = loginObj.token; 54 | done(null, true); 55 | }, function(err) { 56 | done(err, false); 57 | }); 58 | } 59 | } 60 | }; 61 | } 62 | 63 | function askForOTPCode(data) { 64 | return { 65 | when: function() { 66 | if (data.shouldAskForOTPCode) { 67 | console.log(kbString.success([ 68 | 'OTP detected. You should recieve a temp code to authenticate[', 69 | data.OTPMethod, 70 | '].' 71 | ])); 72 | } 73 | return data.shouldAskForOTPCode; 74 | }, 75 | type: 'password', 76 | name: 'GitHubOTP', 77 | message: 'enter two-factor code:', 78 | validate: function(currOTP, userAnswers) { 79 | var done = this.async(); 80 | var pass = currOTP.match(/^\d+$/); 81 | 82 | if (_.isEmpty(currOTP)) { 83 | done('OTP should not be empty', false); 84 | return; 85 | } 86 | 87 | if (!pass) { 88 | done('OTP should contain only numbers', false); 89 | return; 90 | } 91 | 92 | kbGithub.loginGitHub( 93 | userAnswers.GitHubUsername, 94 | userAnswers.GitHubPassword, 95 | currOTP) 96 | .then(function(loginObj) { 97 | data.token = loginObj.token; 98 | done(null, true); 99 | }, function(err) { 100 | done(err, false); 101 | }); 102 | } 103 | }; 104 | } 105 | 106 | function getGitHubUserData(username, token) { 107 | var user = {}; 108 | return kbGithub.getGitHubBasicUserData(username, token) 109 | .then(function(githubUser) { 110 | user.email = githubUser.email; 111 | return kbGithub.getGitHubUserOrgs(username, token); 112 | }) 113 | .then(function(userOrgs) { 114 | user.orgs = userOrgs; 115 | user.username = username; 116 | user.token = token; 117 | 118 | return user; 119 | }); 120 | } 121 | 122 | function getGitHubBasicUserData(username, token) { 123 | var deferred = Q.defer(); 124 | 125 | var githubUserClient = github.client(token); 126 | 127 | githubUserClient.get('/user', {}, function (err, status, body) { 128 | if (err) { 129 | // console.log('ERROR!', err); 130 | deferred.reject('can\'t get user data from github: ' + err); 131 | } else { 132 | // console.log(body); //json object 133 | deferred.resolve(body); 134 | } 135 | }); 136 | 137 | return deferred.promise; 138 | } 139 | 140 | function getGitHubUserOrgs(username, token) { 141 | var deferred = Q.defer(); 142 | 143 | var githubUserClient = github.client(token); 144 | 145 | var ghme = githubUserClient.me(); 146 | 147 | ghme.orgs(function(err, body) { 148 | if (err) { 149 | // console.log('ERROR!', err); 150 | deferred.reject('can\'t get organizations data from github'); 151 | } else { 152 | var orgNames = _.map(body, function(org) { 153 | return org.login; 154 | }); 155 | deferred.resolve(orgNames); 156 | } 157 | }); 158 | 159 | return deferred.promise; 160 | } 161 | 162 | function userQuestionsGithubLogin() { 163 | var data = {}; 164 | return inquirer.prompt([ 165 | kbGithub.questions.askForGitHubUser(data), 166 | kbGithub.questions.askForGitHubPassword(data), 167 | kbGithub.questions.askForOTPCode(data) 168 | ]) 169 | .then(function(answers) { 170 | data.user = { 171 | username: answers.GitHubUsername, 172 | token: data.token 173 | }; 174 | 175 | console.log(data); 176 | 177 | return kbGithub.getGitHubUserData(data.user.username, data.user.token); 178 | }); 179 | } 180 | 181 | function loginGitHub(username, password, otpCode) { 182 | var deferred = Q.defer(); 183 | 184 | var scopes = { 185 | 'scopes': ['user', 'repo', 'gist', 'read:org'], 186 | 'note': [ 187 | 'kibibit cli','@', 188 | os.hostname(), 189 | ' on ', os.type() 190 | ].join('') 191 | }; 192 | 193 | var data = { 194 | username: username, 195 | password: password 196 | }; 197 | 198 | if (otpCode) { 199 | data.otp = otpCode; 200 | } 201 | 202 | github.auth.config(data).login(scopes, function(err, id, token, headers) { 203 | var err = _.get(err, 'message'); 204 | if (_.isEmpty(err)) { 205 | deferred.resolve({ 206 | msg: null, 207 | needOTP: false, 208 | token: token 209 | }); 210 | 211 | return deferred.promise; 212 | } 213 | if (err && err.indexOf('OTP code') > -1) { 214 | deferred.resolve({ 215 | msg: 'OTP detected. You should recieve a temp code to authenticate.', 216 | needOTP: true, 217 | OTPMethod: headers['x-github-otp'].replace('required; ', '') 218 | }); 219 | 220 | return deferred.promise; 221 | } else { 222 | deferred.reject(err); 223 | 224 | return deferred.promise; 225 | } 226 | }); 227 | 228 | return deferred.promise; 229 | } 230 | 231 | function createRepoUser(user, name, description) { 232 | var deferred = Q.defer(); 233 | 234 | var client = github.client(user.token); 235 | 236 | description = description || ''; 237 | 238 | if (!name) { 239 | deferred.reject('name must be given'); 240 | 241 | return deferred.promise; 242 | } 243 | 244 | var ghme = client.me(); 245 | 246 | ghme.repo({ 247 | 'name': name, 248 | 'description': description, 249 | }, function(err, body) { 250 | if (err) { 251 | // console.log('ERROR!', err); 252 | 253 | deferred.reject('can\'t create user repo'); 254 | } else { 255 | deferred.resolve(body); 256 | } 257 | }); //repo 258 | 259 | return deferred.promise; 260 | } 261 | 262 | function createRepoOrg(user, orgName, name, description) { 263 | var deferred = Q.defer(); 264 | 265 | var client = github.client(user.token); 266 | 267 | description = description || ''; 268 | 269 | if (!name || !orgName) { 270 | deferred.reject('orgName & name must be given'); 271 | 272 | return deferred.promise; 273 | } 274 | 275 | var ghorg = client.org(orgName); 276 | 277 | ghorg.repo({ 278 | name: name, 279 | description: description 280 | }, function(err, body) { 281 | if (err) { 282 | // console.log('ERROR!', err); 283 | deferred.reject('can\'t create organization repo'); 284 | } else { 285 | deferred.resolve(body); 286 | } 287 | }); 288 | 289 | return deferred.promise; 290 | } 291 | -------------------------------------------------------------------------------- /lib/gitflow/status.js: -------------------------------------------------------------------------------- 1 | var kbString = require('../kb-string'); 2 | var gitRoot = require('../kb-git-root'); 3 | // var fs = require('fs'); 4 | // var currentFolder = process.cwd(); 5 | var NodeGit = require('nodegit-flow')(require('nodegit')); 6 | var Table = require('cli-table'); 7 | var _ = require('lodash'); 8 | var path = require('path'); 9 | // var colorize = require('json-colorizer'); 10 | var Q = require('q'); 11 | // var findRoot = require('find-root'); 12 | // var countFiles = require('count-files'); 13 | 14 | var GLOBAL = {}; 15 | 16 | var initGitflow = {}; 17 | 18 | initGitflow.status = status; 19 | initGitflow.getStatusAsArray = getStatus; 20 | 21 | var globalRepo, globalDiff; 22 | 23 | module.exports = initGitflow; 24 | 25 | function status() { 26 | gitRoot.getGitRoot() 27 | .then(function(_gitRoot) { 28 | if (!_gitRoot) { 29 | console.info(kbString.error('git repo not found')); 30 | process.exit(1); 31 | } 32 | 33 | GLOBAL.gitRoot = _gitRoot; 34 | 35 | GLOBAL.stagedTable = new Table({ 36 | chars: { 37 | 'top': '═', 38 | 'top-mid': '╤', 39 | 'top-left': '╔', 40 | 'top-right': '╗', 41 | 'bottom': '═', 42 | 'bottom-mid': '╧', 43 | 'bottom-left': '╚', 44 | 'bottom-right': '╝', 45 | 'left': '║', 46 | 'left-mid': '╟', 47 | 'mid': '─', 48 | 'mid-mid': '┼', 49 | 'right': '║', 50 | 'right-mid': '╢', 51 | 'middle': '│' 52 | }, 53 | head: ['filename', 'state', 'diff'] 54 | }); 55 | 56 | GLOBAL.unstagedTable = new Table({ 57 | chars: { 58 | 'top': '═', 59 | 'top-mid': '╤', 60 | 'top-left': '╔', 61 | 'top-right': '╗', 62 | 'bottom': '═', 63 | 'bottom-mid': '╧', 64 | 'bottom-left': '╚', 65 | 'bottom-right': '╝', 66 | 'left': '║', 67 | 'left-mid': '╟', 68 | 'mid': '─', 69 | 'mid-mid': '┼', 70 | 'right': '║', 71 | 'right-mid': '╢', 72 | 'middle': '│' 73 | }, 74 | head: ['filename', 'state', 'diff'] 75 | }); 76 | 77 | return getStatus(); 78 | }) 79 | .then(function(statuses) { 80 | console.log(kbString.success( 81 | 'BRANCH NAME: [', 82 | kbString.white(statuses.branchName), 83 | ']' 84 | )); 85 | 86 | var seenFolderStaged = false; 87 | _.forEach(statuses.staged, function(file) { 88 | seenFolderStaged = seenFolderStaged || file.isFolder; 89 | var colored = file.isFolder ? kbString.success : kbString.info; 90 | GLOBAL.stagedTable.push([ 91 | colored(!file.isFolder && seenFolderStaged ? ' ' : '', file.path), 92 | file.statusStr, 93 | file.changes ? kbString.build([ 94 | kbString.success('++', file.changes[0]), 95 | ' | ', 96 | kbString.error('++', file.changes[1]) 97 | ]) : '' 98 | ]); 99 | }); 100 | 101 | var seenFolderUnstaged = false; 102 | _.forEach(statuses.unstaged, function(file) { 103 | seenFolderUnstaged = seenFolderUnstaged || file.isFolder; 104 | var colored = file.isFolder ? kbString.success : kbString.info; 105 | GLOBAL.unstagedTable.push([ 106 | colored(!file.isFolder && seenFolderUnstaged ? ' ' : '', file.path), 107 | file.statusStr, 108 | file.changes ? kbString.build([ 109 | kbString.success('++', file.changes[0]), 110 | ' | ', 111 | kbString.error('--', file.changes[1]) 112 | ]) : '' 113 | ]); 114 | }); 115 | 116 | console.log(kbString.build([ 117 | '\n================\n', 118 | '= ', kbString.info('STAGED FILES'), ' =\n', 119 | '================' 120 | ])); 121 | console.log(GLOBAL.stagedTable.length ? 122 | GLOBAL.stagedTable.toString() : 123 | kbString.info('nothing to show here')); 124 | console.log(kbString.build([ 125 | '\n==================\n', 126 | '= ', kbString.warning('UNSTAGED FILES'), ' =\n', 127 | '==================' 128 | ])); 129 | console.log(GLOBAL.unstagedTable.length ? 130 | GLOBAL.unstagedTable.toString() : 131 | kbString.info('nothing to show here')); 132 | 133 | process.exit(0); 134 | }) 135 | .catch(function(error) { 136 | console.log(error); 137 | process.exit(1); 138 | }); 139 | } 140 | 141 | function getStatus(shouldAddFolderEntries, shouldGroupBy) { 142 | shouldGroupBy = shouldGroupBy || 'isStaged'; 143 | var deferred = Q.defer(); 144 | 145 | // GLOBAL PROCESS VARIALBES 146 | var currentBranchName = ''; 147 | var currentBranchStatuses; 148 | 149 | var files = []; 150 | gitRoot.getGitRoot() 151 | .then(function(_gitRoot) { 152 | return NodeGit.Repository.open(_gitRoot); 153 | }) 154 | .then(function(repo) { 155 | globalRepo = repo; 156 | 157 | return Q.all([ 158 | globalRepo.getStatus(), 159 | globalRepo.getCurrentBranch(), 160 | getDiff(globalRepo) 161 | ]); 162 | }) 163 | .then(function(result) { 164 | currentBranchStatuses = result[0]; 165 | globalDiff = result[2]; 166 | return NodeGit.Branch.name(result[1]); 167 | }) 168 | .then(function(currBranchName) { 169 | var statuses = currentBranchStatuses; 170 | currentBranchName = currBranchName; 171 | 172 | statuses.forEach(function(file) { 173 | files.push({ 174 | path: file.path(), 175 | status: statusToArray(file), 176 | statusStr: statusToText(file), 177 | folder: path.dirname(file.path()), 178 | isStaged: file.status()[0].indexOf('INDEX') >= 0, 179 | changes: globalDiff[file.path()] 180 | }); 181 | }); 182 | 183 | var sortedFiles = _.sortBy(files, [ 'path' ]); 184 | 185 | // separate to staged vs unstaged 186 | if (shouldGroupBy) { 187 | var separatedByStage = _.groupBy(sortedFiles, shouldGroupBy); 188 | } 189 | 190 | if (shouldAddFolderEntries) { 191 | separatedByStage.true = addFolderEntries(separatedByStage.true); 192 | separatedByStage.false = addFolderEntries(separatedByStage.false); 193 | } 194 | 195 | deferred.resolve({ 196 | staged: separatedByStage.true, 197 | unstaged: separatedByStage.false, 198 | branchName: currentBranchName 199 | }); 200 | }) 201 | .catch(function(err) { 202 | console.error('couldn\'t open repo', err); 203 | throw err; 204 | deferred.reject(err); 205 | }); 206 | 207 | return deferred.promise; 208 | } 209 | 210 | function addFolderEntries(files) { 211 | files = _.sortBy(files, [ 'path' ]); 212 | 213 | var filesWithFolders = []; 214 | var currentFolder = '.'; 215 | _.forEach(files, function(file) { 216 | if (file.folder === currentFolder || file.folder === currentFolder + '/') { 217 | filesWithFolders.push(file); 218 | } else { 219 | filesWithFolders.push({ 220 | path: file.folder + '/', 221 | status: '', 222 | statusStr: '', 223 | isFolder: true 224 | }); 225 | currentFolder = file.folder; 226 | filesWithFolders.push(file); 227 | } 228 | }); 229 | 230 | return filesWithFolders; 231 | } 232 | 233 | function statusToArray(status) { 234 | var words = []; 235 | if (status.isNew()) { 236 | words.push('NEW'); 237 | } 238 | if (status.isModified()) { 239 | words.push('MODIFIED'); 240 | } 241 | if (status.isTypechange()) { 242 | words.push('TYPECHANGE'); 243 | } 244 | if (status.isRenamed()) { 245 | words.push('RENAMED'); 246 | } 247 | if (status.isIgnored()) { 248 | words.push('IGNORED'); 249 | } 250 | 251 | return words; 252 | } 253 | 254 | function statusToText(status) { 255 | var words = []; 256 | if (status.isNew()) { 257 | words.push(kbString.build( 258 | kbString.white('['), 259 | kbString.info('NEW'), 260 | kbString.white(']') 261 | )); 262 | } 263 | if (status.isModified()) { 264 | words.push(kbString.build( 265 | kbString.white('['), 266 | kbString.warning('MODIFIED'), 267 | kbString.white(']') 268 | )); 269 | } 270 | if (status.isTypechange()) { 271 | words.push(kbString.build( 272 | kbString.white('['), 273 | kbString.warning('TYPECHANGE'), 274 | kbString.white(']') 275 | )); 276 | } 277 | if (status.isRenamed()) { 278 | words.push(kbString.build( 279 | kbString.white('['), 280 | kbString.error('RENAMED'), 281 | kbString.white(']') 282 | )); 283 | } 284 | if (status.isIgnored()) { 285 | words.push(kbString.build( 286 | kbString.white('['), 287 | kbString.white('IGNORED'), 288 | kbString.white(']') 289 | )); 290 | } 291 | 292 | return words.join(' '); 293 | } 294 | 295 | function getDiff(repo) { 296 | var deferred = Q.defer(); 297 | 298 | var result = {}; 299 | 300 | NodeGit.Diff.indexToWorkdir(repo) 301 | .then(function(diff) { 302 | return diff.patches(); 303 | }) 304 | .then(function(patches) { 305 | _.forEach(patches, function(patch) { 306 | var lineStats = patch.lineStats(); 307 | 308 | result[patch.newFile().path()] = [ 309 | lineStats.total_additions, 310 | lineStats.total_deletions 311 | ]; 312 | }); 313 | 314 | deferred.resolve(result); 315 | }) 316 | .catch(function(error) { 317 | deferred.reject(error); 318 | console.error(error); 319 | }); 320 | 321 | return deferred.promise; 322 | } 323 | -------------------------------------------------------------------------------- /lib/commandDefinitions.js: -------------------------------------------------------------------------------- 1 | var kbString = require('./kb-string'); 2 | var initGitflow = require('./gitflow/init'); 3 | var statusGitflow = require('./gitflow/status'); 4 | var commitGitflow = require('./gitflow/commit'); 5 | var finishGitflow = require('./gitflow/finish'); 6 | var featureGitflow = require('./gitflow/feature'); 7 | var releaseGitflow = require('./gitflow/release'); 8 | var hotfixGitflow = require('./gitflow/hotfix'); 9 | var developGitflow = require('./gitflow/develop'); 10 | var syncGitflow = require('./gitflow/sync'); 11 | var masterGitflow = require('./gitflow/master'); 12 | // var inquirer = require('inquirer'); 13 | var shell = require('shelljs'); 14 | // var Q = require('q'); 15 | // var Git = require('nodegit'); 16 | // var colorize = require('json-colorizer'); 17 | // var _ = require('lodash'); 18 | // var github = require('octonode'); 19 | // var currentFolder = process.cwd(); 20 | // var signingIn = [ 21 | // kbString.info(kbString.warning('/'), ' Signing in'), 22 | // kbString.info(kbString.warning('|'), ' Signing in..'), 23 | // kbString.info(kbString.warning('\\'), ' Signing in..'), 24 | // kbString.info(kbString.warning('-'), ' Signing in...') 25 | // ]; 26 | // var signingInSteps = 4; 27 | 28 | // var client = github.client(); 29 | 30 | var commandDefinitions = {}; 31 | 32 | commandDefinitions.attach = function attachCommands(program) { 33 | addSubCommandCommit(program); 34 | addSubCommandStatus(program); 35 | addSubCommandInit(program); 36 | addSubCommandClone(program); 37 | addSubCommandFeature(program); 38 | addSubCommandHotfix(program); 39 | addSubCommandFinish(program); 40 | addSubCommandRelease(program); 41 | addSubCommandUpdate(program); 42 | addSubCommandPush(program); 43 | addSubCommandMaster(program); 44 | addSubCommandDevelop(program); 45 | }; 46 | 47 | function addSubCommandMaster(program) { 48 | program 49 | .command('master') 50 | .description(kbString.info('checkout master branch')) 51 | .action(masterGitflow.develop) 52 | .on('help', function(cmd) { 53 | cmd.outputIndented('Examples', [ 54 | kbString.build( 55 | kbString.success('$ '), 56 | kbString.kibibitLogo(), 57 | kbString.success(' master ') 58 | ) 59 | ]); 60 | }).parent; 61 | } 62 | 63 | function addSubCommandDevelop(program) { 64 | program 65 | .command('develop') 66 | .description(kbString.info('checkout develop branch')) 67 | .action(developGitflow.develop) 68 | .on('help', function(cmd) { 69 | cmd.outputIndented('Examples', [ 70 | kbString.build( 71 | kbString.success('$ '), 72 | kbString.kibibitLogo(), 73 | kbString.success(' develop ') 74 | ) 75 | ]); 76 | }).parent; 77 | } 78 | 79 | function addSubCommandCommit(program) { 80 | program 81 | .command('commit') 82 | .description(kbString.info('clone a remote repository')) 83 | .action(commitGitflow.commit) 84 | .option('-v, --verbose', 'only show my features') 85 | .option('-S, --gpg-sign', 'GPG sign commit') 86 | .on('help', function(cmd) { 87 | cmd.outputIndented('Examples', [ 88 | kbString.build( 89 | kbString.success('$ '), 90 | kbString.kibibitLogo(), 91 | kbString.success(' commit '), 92 | kbString.param('search-db') 93 | ), 94 | kbString.build( 95 | kbString.success('$ '), 96 | kbString.kibibitLogo(), 97 | kbString.success(' commit') 98 | ), 99 | kbString.build( 100 | kbString.success('$ '), 101 | kbString.kibibitLogo(), 102 | kbString.success(' commit '), 103 | '-v' 104 | ) 105 | ]); 106 | }).parent; 107 | } 108 | 109 | function addSubCommandClone(program) { 110 | program 111 | .command('clone ') 112 | .description(kbString.info('clone a remote repository')) 113 | .action(clone) 114 | .option('-v, --verbose', 'only show my features') 115 | .option('-S, --gpg-sign', 'GPG sign commit') 116 | .on('help', function(cmd) { 117 | cmd.outputIndented('Examples', [ 118 | kbString.build( 119 | kbString.success('$ '), 120 | kbString.kibibitLogo(), 121 | kbString.success(' commit '), 122 | kbString.param('search-db') 123 | ), 124 | kbString.build( 125 | kbString.success('$ '), 126 | kbString.kibibitLogo(), 127 | kbString.success(' commit') 128 | ), 129 | kbString.build( 130 | kbString.success('$ '), 131 | kbString.kibibitLogo(), 132 | kbString.success(' commit '), 133 | '-v' 134 | ) 135 | ]); 136 | }).parent; 137 | } 138 | 139 | function addSubCommandStatus(program) { 140 | program 141 | .command('status') 142 | .description(kbString.info('show current branch status')) 143 | .action(statusGitflow.status) 144 | .option('-v, --verbose', 'only show my features') 145 | .option('-S, --gpg-sign', 'GPG sign commit') 146 | .on('help', function(cmd) { 147 | cmd.outputIndented('Examples', [ 148 | kbString.build( 149 | kbString.success('$ '), 150 | kbString.kibibitLogo(), 151 | kbString.success(' status ') 152 | ), 153 | kbString.build( 154 | kbString.success('$ '), 155 | kbString.kibibitLogo(), 156 | kbString.success(' status') 157 | ), 158 | kbString.build( 159 | kbString.success('$ '), 160 | kbString.kibibitLogo(), 161 | kbString.success(' status ') 162 | ) 163 | ]); 164 | }).parent; 165 | } 166 | 167 | function addSubCommandFeature(program) { 168 | program 169 | .command('feature [featureName]') 170 | .description(kbString.build([ 171 | kbString.info('start or continue a feature '), 172 | kbString.warning('(will be prompted)'), 173 | kbString.info( 174 | '. If no featureName is given, returns all ongoing features' 175 | ) 176 | ])) 177 | .action(featureGitflow.feature) 178 | .option('-m, --mine', 'only show my features') 179 | .option('-r, --remote', 'only show remote features') 180 | .on('help', function(cmd) { 181 | cmd.outputIndented('Examples', [ 182 | kbString.build( 183 | kbString.success('$ '), 184 | kbString.kibibitLogo(), 185 | kbString.success(' feature '), 186 | kbString.param('search-db') 187 | ), 188 | kbString.build( 189 | kbString.success('$ '), 190 | kbString.kibibitLogo(), 191 | kbString.success(' feature') 192 | ), 193 | kbString.build( 194 | kbString.success('$ '), 195 | kbString.kibibitLogo(), 196 | kbString.success(' feature '), 197 | '-m' 198 | ) 199 | ]); 200 | }).parent; 201 | } 202 | 203 | function addSubCommandHotfix(program) { 204 | program 205 | .command('hotfix [hotfixName]') 206 | .description(kbString.build([ 207 | kbString.info('start or continue a hotfix '), 208 | kbString.warning('(will be prompted)'), 209 | kbString.info('. If no hotfixName is given, returns all ongoing hotfixes') 210 | ])) 211 | .action(hotfixGitflow.hotfix) 212 | .option('-m, --mine', 'only show my hotfixes') 213 | .on('help', function(cmd) { 214 | cmd.outputIndented('Examples', [ 215 | kbString.build( 216 | kbString.success('$ '), 217 | kbString.kibibitLogo(), 218 | kbString.success(' hotfix '), 219 | kbString.param('search-db-edge-case-no-query') 220 | ), 221 | kbString.build( 222 | kbString.success('$ '), 223 | kbString.kibibitLogo(), 224 | kbString.success(' hotfix') 225 | ), 226 | kbString.build( 227 | kbString.success('$ '), 228 | kbString.kibibitLogo(), 229 | kbString.success(' hotfix '), 230 | '-m' 231 | ) 232 | ]); 233 | }).parent; 234 | } 235 | 236 | function addSubCommandFinish(program) { 237 | program 238 | .command('finish') 239 | .description( 240 | kbString.info('use GitHub to issue a pull request to origin/develop.') 241 | ) 242 | .action(finishGitflow.finish) 243 | .on('help', function(cmd) { 244 | cmd.outputIndented('Examples', [ 245 | kbString.build( 246 | kbString.success('$ '), 247 | kbString.kibibitLogo(), 248 | kbString.success(' finish ') 249 | ) 250 | ]); 251 | }).parent; 252 | } 253 | 254 | function addSubCommandRelease(program) { 255 | program 256 | .command('release [releaseName]') 257 | .description(kbString.build( 258 | kbString.info( 259 | 'When you have enough completed features in origin/develop, ' 260 | ), 261 | kbString.info('create a release branch, test it and fix it, '), 262 | kbString.info('and then merge it into origin/master') 263 | )) 264 | .action(releaseGitflow.release) 265 | .on('help', function(cmd) { 266 | cmd.outputIndented('Examples', [ 267 | kbString.build( 268 | kbString.success('$ '), 269 | kbString.kibibitLogo(), 270 | kbString.success(' release '), 271 | kbString.param('start') 272 | ), 273 | kbString.success(''), 274 | kbString.build( 275 | kbString.success('$ '), 276 | kbString.kibibitLogo(), 277 | kbString.success(' release '), 278 | kbString.param('finish') 279 | ) 280 | ]); 281 | }).parent; 282 | } 283 | 284 | function addSubCommandUpdate(program) { 285 | program 286 | .command('update') 287 | .description( 288 | kbString.info('keep up-to-date with completed features on GitHub') 289 | ) 290 | .action(syncGitflow.sync) 291 | .on('help', function(cmd) { 292 | cmd.outputIndented('Examples', [ 293 | kbString.build( 294 | kbString.success('$ '), 295 | kbString.kibibitLogo(), 296 | kbString.success(' update') 297 | ) 298 | ]); 299 | }).parent; 300 | } 301 | 302 | function addSubCommandPush(program) { 303 | program 304 | .command('push') 305 | .description( 306 | kbString.info([ 307 | 'push your feature branch back to GitHub as you make progress ', 308 | 'and want to save your work' 309 | ]) 310 | ) 311 | .action(push) 312 | .on('help', function(cmd) { 313 | cmd.outputIndented('Examples', [ 314 | kbString.build( 315 | kbString.success('$ '), 316 | kbString.kibibitLogo(), 317 | kbString.success(' push') 318 | ) 319 | ]); 320 | }).parent; 321 | } 322 | 323 | function addSubCommandInit(program) { 324 | program 325 | .command('init') 326 | .description( 327 | kbString.info('initialize the gitflow tools for the current repo. ') + 328 | kbString.warning('(GitHub login, etc.)') 329 | ) 330 | .option('-f, --force', kbString.build([ 331 | 'force setting of hubflow branches, even if ', 332 | 'already configured (default: false)' 333 | ])) 334 | .option('-a, --ask', 'ask for branch naming conventions (default: false)') 335 | .action(initGitflow.init) 336 | .on('help', function(cmd) { 337 | cmd.outputIndented('Examples', [ 338 | kbString.build( 339 | kbString.success('$ '), 340 | kbString.kibibitLogo(), 341 | kbString.success(' init ') 342 | ), 343 | kbString.build( 344 | kbString.success('$ '), 345 | kbString.kibibitLogo(), 346 | kbString.success(' init '), 347 | '-f' 348 | ), 349 | kbString.build( 350 | kbString.success('$ '), 351 | kbString.kibibitLogo(), 352 | kbString.success(' init '), 353 | '--ask' 354 | ) 355 | ]); 356 | }).parent; 357 | } 358 | 359 | module.exports = commandDefinitions; 360 | 361 | function clone() { 362 | console.error('clone!'); 363 | } 364 | 365 | function push() { 366 | kbExec('git hf push'); 367 | } 368 | 369 | // function update() { 370 | // kbExec('git hf update'); 371 | // } 372 | 373 | function kbExec(command) { 374 | if (shell.exec(command).code !== 0) { 375 | shell.echo('Error: ' + kbString.error(command) + ' failed'); 376 | shell.exit(1); 377 | } 378 | } 379 | -------------------------------------------------------------------------------- /lib/services/kb-repo.js: -------------------------------------------------------------------------------- 1 | /*eslint-env es6*/ 2 | var KbError = require('../errors/KbError'); 3 | var gitRoot = require('../kb-git-root'); 4 | var NodeGit = require('nodegit-flow')(require('nodegit')); 5 | var inquirer = require('inquirer'); 6 | var colorize = require('json-colorizer'); 7 | var kbString = require('../kb-string'); 8 | var kbGithub = require('./kb-github'); 9 | var _ = require('lodash'); 10 | var writeFile = require('write-file-promise'); 11 | var readFile = require('fs-readfile-promise'); 12 | var Q = require('q'); 13 | var path = require('path'); 14 | 15 | Q.longStackSupport = true; 16 | 17 | var kbRepo = {}; 18 | 19 | kbRepo.getCurrentGitFilesStatus = getStatus; 20 | kbRepo.commitFilesByPath = commitFilesByPath; 21 | kbRepo.getBitConfig = getBitConfig; 22 | kbRepo.saveBitConfig = saveBitConfig; 23 | kbRepo.openGit = openGit; 24 | kbRepo.initGit = initGit; 25 | kbRepo.ensureGitFlowNotInitialized = ensureGitFlowNotInitialized; 26 | kbRepo.ensureBasicGitFlowDetails = ensureBasicGitFlowDetails; 27 | kbRepo.setGitConfigUser = setGitConfigUser; 28 | 29 | kbRepo.questions = {}; 30 | kbRepo.questions.masterBranch = askNameOfMasterBranch(); 31 | kbRepo.questions.developBranch = askNameOfDevelopBranch(); 32 | kbRepo.questions.featureBranchPrefix = askPrefixOfFeatureBranches(); 33 | kbRepo.questions.hotfixBranchPrefix = askPrefixOfHotfixBranches(); 34 | kbRepo.questions.releaseBranchPrefix = askPrefixOfReleaseBranches(); 35 | kbRepo.questions.shouldSaveBitConfig = askIfShouldSaveBitConfig(); 36 | kbRepo.questions.createRepoOrigin = createRepoOrigin; 37 | 38 | module.exports = kbRepo; 39 | 40 | function getBitConfig(rootFolder) { 41 | console.log('trying to read file', rootFolder + '/.bit.json'); 42 | return readFile(rootFolder + '/.bit.json', 'utf8'); 43 | } 44 | 45 | function saveBitConfig(rootFolder, config) { 46 | var configStr = JSON.stringify(config, null, 2); 47 | return writeFile(rootFolder + '/.bit.json', configStr) 48 | .catch(function(error) { 49 | console.error('something went wrong!', error); 50 | }); 51 | } 52 | 53 | function setGitConfigUser(repo, user) { 54 | var data = {}; 55 | return repo.config() 56 | .then(function(config) { 57 | data.config = config; 58 | return data.config.setString('user.name', user.username); 59 | }) 60 | .then(function() { 61 | return data.config.setString('user.email', user.email); 62 | }); 63 | } 64 | 65 | function ensureBasicGitFlowDetails(rootFolder, repo, user) { 66 | var data = {}; 67 | console.log('getting bitconfig'); 68 | // check if bit config exists. if so, use it. else, ask user for details 69 | // and check if he wants to save those details globally for all users 70 | return kbRepo.getBitConfig(rootFolder) 71 | .then(function(repoBitConfig) { 72 | console.log('found config file', repoBitConfig); 73 | console.log('checking validity...'); 74 | 75 | return checkConfigSchema(repoBitConfig); 76 | }) 77 | .then(function(config) { 78 | data.userOptions = config; 79 | }) 80 | .catch(function() { 81 | console.log('valid bit config not found'); 82 | return inquirer.prompt([ 83 | kbRepo.questions.masterBranch, 84 | kbRepo.questions.developBranch, 85 | kbRepo.questions.featureBranchPrefix, 86 | kbRepo.questions.hotfixBranchPrefix, 87 | kbRepo.questions.releaseBranchPrefix, 88 | kbRepo.questions.shouldSaveBitConfig 89 | ]) 90 | .then(function(answers) { 91 | 92 | answers.featureBranchesPrefix = 93 | ensureSlash(answers.featureBranchesPrefix); 94 | answers.releaseBranchesPrefix = 95 | ensureSlash(answers.releaseBranchesPrefix); 96 | answers.hotfixBranchesPrefix = 97 | ensureSlash(answers.hotfixBranchesPrefix); 98 | 99 | console.log(kbString.build([ 100 | '\nnew ', kbString.kibibitLogo(), ' gitflow repo will be ', 101 | 'initialized with these settings:' 102 | ])); 103 | console.log(colorize(JSON.stringify(answers, null, ' '))); 104 | 105 | data.userOptions = { 106 | 'gitflow.branch.master': answers.masterBranch, 107 | 'gitflow.branch.develop': answers.developBranch, 108 | 'gitflow.prefix.feature': answers.featureBranchesPrefix, 109 | 'gitflow.prefix.release': answers.releaseBranchesPrefix, 110 | 'gitflow.prefix.hotfix': answers.hotfixBranchesPrefix, 111 | 'gitflow.prefix.versiontag': '' 112 | }; 113 | 114 | // console.log('should save?', answers.shouldSaveBitConfig); 115 | data.shouldSaveBitConfig = answers.shouldSaveBitConfig; 116 | data.configPath = rootFolder + '/.bit.json'; 117 | 118 | return data.shouldSaveBitConfig && 119 | kbRepo.saveBitConfig(rootFolder, data.userOptions); 120 | }); 121 | }) 122 | .then(function() { 123 | 124 | // here, we need to make sure that master branch exists 125 | // and have an initial commit 126 | return repo.getBranchCommit('master') 127 | // no initial commit? 128 | // no problem. make one! 129 | .catch(makeInitialCommit(repo, 130 | user, data.shouldSaveBitConfig, data.configPath)); 131 | }) 132 | .then(function(commit) { 133 | commit = commit.commit || commit; 134 | if (data.userOptions['gitflow.branch.master'] !== 'master') { 135 | return repo.createBranch( 136 | data.userOptions['gitflow.branch.master'], commit); 137 | } 138 | 139 | return commit; 140 | }) 141 | .then(function(/* commit */) { 142 | // we now have all the user preferences and we can 143 | // initialize git-flow into the repo 144 | return NodeGit.Flow.init(repo, data.userOptions); 145 | }) 146 | .then(function() { 147 | return repo.checkoutBranch(data.userOptions['gitflow.branch.master']) 148 | .then(function() { 149 | if (data.userOptions['gitflow.branch.master'] !== 'master') { 150 | return repo.getBranch('master'); 151 | } 152 | 153 | return; 154 | }) 155 | .then(function(oldMasterBranch) { 156 | if (!oldMasterBranch) { return; } 157 | var result = NodeGit.Branch.delete(oldMasterBranch); 158 | console.log('deleted old master branch', result); 159 | }); 160 | }); 161 | } 162 | 163 | function createRepoOrigin(repo, user) { 164 | var data = {}; 165 | return inquirer.prompt([{ 166 | type: 'list', 167 | name: 'whereToCreate', 168 | message: 'Where should repo be created?', 169 | choices: [ 170 | user.username, 171 | new inquirer.Separator() 172 | ].concat(user.orgs) 173 | }, 174 | { 175 | type: 'input', 176 | name: 'repoName', 177 | message: 'what do you want to name the repo?', 178 | }, 179 | { 180 | type: 'input', 181 | name: 'repoDescription', 182 | message: 'give the repo a short description (optional)', 183 | }]) 184 | .then(function(answers) { 185 | data.repoName = answers.repoName; 186 | data.repoDescription = answers.repoDescription; 187 | 188 | if (answers.whereToCreate === user.username) { 189 | return kbGithub.createRepoUser(user, 190 | data.repoName, data.repoDescription); 191 | } else { 192 | return kbGithub.createRepoOrg(user, answers.whereToCreate, 193 | data.repoName, data.repoDescription); 194 | } 195 | }) 196 | .then(function(body) { 197 | console.log('created repo', body.clone_url); 198 | 199 | // OR ORIGIN!!!!!! INSTEAD OF NAME 200 | return NodeGit.Remote 201 | .create(repo, 'origin', body.clone_url); 202 | }) 203 | .then(function(remote) { 204 | data.remote = remote; 205 | // console.log('successfully added a new remote!', data.remote.name()); 206 | 207 | return NodeGit.Flow.getConfig(repo); 208 | }) 209 | .then(function(config) { 210 | var master = config['gitflow.branch.master']; 211 | var develop = config['gitflow.branch.develop']; 212 | return data.remote.push([ 213 | `refs/heads/${master}:refs/heads/${master}`, 214 | `refs/heads/${develop}:refs/heads/${develop}` 215 | ], { 216 | callbacks: { 217 | certificateCheck: function() { 218 | return 1; 219 | }, 220 | credentials: function() { 221 | // console.log('asked for credentials', user.token); 222 | return NodeGit.Cred 223 | .userpassPlaintextNew(user.token, 'x-oauth-basic'); 224 | } 225 | } 226 | }); 227 | }); 228 | } 229 | 230 | function commitFilesByPath(repo, options) { 231 | var index, parents; 232 | var returnData = {}; 233 | return repo.refreshIndex() 234 | .then(function(idx) { 235 | index = idx; 236 | 237 | return options.filesToCommit ? 238 | addFilesToIndex(index, options.filesToCommit) : 239 | true; 240 | }) 241 | .then(function() { 242 | return index.write(); 243 | }) 244 | .then(function() { 245 | return repo.getCurrentBranch() 246 | .catch(function() { 247 | return 'refs/heads/master'; 248 | }); 249 | }) 250 | .then(function(currentBranchRef) { 251 | returnData.branch = currentBranchRef.name ? 252 | currentBranchRef.name() : 253 | currentBranchRef; 254 | return repo.getHeadCommit() 255 | .then(function(commitId) { 256 | return commitId ? [ commitId ] : []; 257 | }) 258 | .catch(function() { 259 | // wasn't able to get the last commit. 260 | // this means this is the first commit. 261 | return []; 262 | }); 263 | }) 264 | .then(function(parentArray) { 265 | parents = parentArray; 266 | // console.log('file added to index?'); 267 | return index.writeTree(); 268 | }) 269 | .then(function(oid) { 270 | var signature = options.user ? 271 | NodeGit.Signature.now(options.user.username, options.user.email) : 272 | NodeGit.Signature.default(repo); 273 | 274 | returnData.oid = oid; 275 | returnData.parents = _.map(parents, (branch) => { return branch.id(); }); 276 | returnData.commitMessage = options.commitMessage || 'initial commit'; 277 | returnData.authorName = signature.name(); 278 | returnData.authorEmail = signature.email(); 279 | returnData.branch = _.replace(returnData.branch, 'refs/heads/', ''); 280 | 281 | return repo.createCommit('HEAD', 282 | signature, 283 | signature, 284 | options.commitMessage || 'initial commit', 285 | oid, 286 | parents); 287 | }) 288 | .then(function(newCommit) { 289 | returnData.commit = newCommit; 290 | return returnData; 291 | }); 292 | } 293 | 294 | function addFilesToIndex(index, filesToCommit) { 295 | filesToCommit = _.isString(filesToCommit) ? [ filesToCommit ] : filesToCommit; 296 | var fName = arguments.callee.toString().match(/function ([^\(]+)/)[1]; 297 | if (!index) { return Q.reject(fName + ' expects an index arg'); } 298 | // add files 299 | return Q 300 | .allSettled(_.map(filesToCommit, function(file) { 301 | return index.addByPath(file); 302 | })); 303 | } 304 | 305 | function makeInitialCommit(repo, user, shouldSaveBitConfig) { 306 | var filesToCommit = shouldSaveBitConfig ? '.bit.json' : undefined; 307 | return function() { 308 | return kbRepo.commitFilesByPath(repo, { 309 | filesToCommit: filesToCommit, 310 | user: user 311 | }); 312 | }; 313 | } 314 | 315 | function checkConfigSchema(configStr) { 316 | var parsedConfig = JSON.parse(configStr); 317 | if (notEmptyString(parsedConfig['gitflow.branch.master']) && 318 | notEmptyString(parsedConfig['gitflow.branch.develop']) && 319 | notEmptyString(parsedConfig['gitflow.prefix.feature']) && 320 | notEmptyString(parsedConfig['gitflow.prefix.release']) && 321 | notEmptyString(parsedConfig['gitflow.prefix.hotfix'])) { 322 | return parsedConfig; 323 | } else { 324 | throw new KbError('bit config file is messed up', 0); 325 | } 326 | } 327 | 328 | function notEmptyString(str) { 329 | return !_.isEmpty(str) && _.isString(str); 330 | } 331 | 332 | function askIfShouldSaveBitConfig() { 333 | return { 334 | type: 'confirm', 335 | name: 'shouldSaveBitConfig', 336 | message: 'should we save this setting globaly so everyone will use it?', 337 | default: true 338 | }; 339 | } 340 | 341 | function askPrefixOfReleaseBranches() { 342 | return { 343 | type: 'input', 344 | name: 'releaseBranchesPrefix', 345 | message: 'Release branches?', 346 | default: 'release/' 347 | }; 348 | } 349 | 350 | function askPrefixOfHotfixBranches() { 351 | return { 352 | type: 'input', 353 | name: 'hotfixBranchesPrefix', 354 | message: 'Hotfix branches?', 355 | default: 'hotfix/' 356 | }; 357 | } 358 | 359 | function askPrefixOfFeatureBranches() { 360 | return { 361 | // when: writeBefore('How to name your supporting branch prefixes?'), 362 | type: 'input', 363 | name: 'featureBranchesPrefix', 364 | message: 'Feature branches?', 365 | default: 'feature/' 366 | }; 367 | } 368 | 369 | function askNameOfDevelopBranch() { 370 | return { 371 | type: 'input', 372 | name: 'developBranch', 373 | message: 'Branch name for "next release" development?', 374 | default: 'develop' 375 | }; 376 | } 377 | 378 | function askNameOfMasterBranch() { 379 | return { 380 | type: 'input', 381 | name: 'masterBranch', 382 | message: 'Branch name for production releases?', 383 | default: 'master' 384 | }; 385 | } 386 | 387 | function ensureGitFlowNotInitialized(repo) { 388 | return NodeGit.Flow.isInitialized(repo) 389 | .then(function(isInit) { 390 | if (isInit) { 391 | throw new Error('repo is already a git flow repo', 1, true); 392 | } 393 | 394 | return isInit; 395 | }); 396 | } 397 | 398 | function initGit(currentFolder) { 399 | return NodeGit.Repository.init(currentFolder, 0); 400 | } 401 | 402 | function openGit() { 403 | return gitRoot.getGitRoot() 404 | .then(function(root) { 405 | console.log('got the root!', root); 406 | return NodeGit.Repository.open(root); 407 | }); 408 | } 409 | 410 | function ensureSlash(prefix) { 411 | return _.endsWith(prefix, '/') ? prefix : prefix + '/'; 412 | } 413 | 414 | function getStatus(repo, shouldAddFolderEntries, shouldGroupBy) { 415 | shouldGroupBy = shouldGroupBy || 'isStaged'; 416 | 417 | // GLOBAL PROCESS VARIALBES 418 | var currentBranchName, currentBranchStatuses, globalDiff; 419 | 420 | var files = []; 421 | return Q.all([ 422 | repo.getStatus(), 423 | repo.getCurrentBranch(), 424 | getDiff(repo) 425 | ]) 426 | .then(function(result) { 427 | currentBranchStatuses = result[0]; 428 | globalDiff = result[2]; 429 | return NodeGit.Branch.name(result[1]); 430 | }) 431 | .then(function(currBranchName) { 432 | var statuses = currentBranchStatuses; 433 | currentBranchName = currBranchName; 434 | 435 | statuses.forEach(function(file) { 436 | files.push({ 437 | path: file.path(), 438 | status: statusToArray(file), 439 | statusStr: statusToText(file), 440 | folder: path.dirname(file.path()), 441 | isStaged: file.status()[0].indexOf('INDEX') >= 0, 442 | changes: globalDiff[file.path()] 443 | }); 444 | }); 445 | 446 | // sort files alphabetically 447 | var sortedFiles = _.sortBy(files, [ 'path' ]); 448 | 449 | // separate to staged vs unstaged 450 | if (shouldGroupBy) { 451 | var separatedByStage = _.groupBy(sortedFiles, shouldGroupBy); 452 | } 453 | 454 | // should we show folders as well? 455 | /* TODO(thatkookooguy): update according 456 | to https://github.com/nodegit/nodegit/issues/1416 */ 457 | if (shouldAddFolderEntries) { 458 | separatedByStage.true = addFolderEntries(separatedByStage.true); 459 | separatedByStage.false = addFolderEntries(separatedByStage.false); 460 | } 461 | 462 | var data = {}; 463 | data.staged = separatedByStage.true; 464 | data.unstaged = separatedByStage.false; 465 | data.branchName = currentBranchName; 466 | // data.all = _.groupBy(data.staged.concat(data.unstaged), 'folder'); 467 | 468 | // console.log('this is what I got!', data); 469 | 470 | //if no changes 471 | if (!data.staged && !data.unstaged) { 472 | throw new KbError(kbString.build([ 473 | 'no changes were found.\n', 474 | 'change some files in order to commit them.' 475 | ]), 1, true); 476 | } 477 | 478 | return data; 479 | }) 480 | .catch(function(err) { 481 | throw err; 482 | }); 483 | } 484 | 485 | function getDiff(repo) { 486 | var deferred = Q.defer(); 487 | 488 | var result = {}; 489 | 490 | NodeGit.Diff.indexToWorkdir(repo) 491 | .then(function(diff) { 492 | return diff.patches(); 493 | }) 494 | .then(function(patches) { 495 | _.forEach(patches, function(patch) { 496 | var lineStats = patch.lineStats(); 497 | 498 | result[patch.newFile().path()] = [ 499 | lineStats.total_additions, 500 | lineStats.total_deletions 501 | ]; 502 | }); 503 | 504 | deferred.resolve(result); 505 | }) 506 | .catch(function(error) { 507 | deferred.reject(error); 508 | console.error(error); 509 | }); 510 | 511 | return deferred.promise; 512 | } 513 | 514 | function statusToArray(status) { 515 | var words = []; 516 | if (status.isNew()) { 517 | words.push('NEW'); 518 | } 519 | if (status.isModified()) { 520 | words.push('MODIFIED'); 521 | } 522 | if (status.isTypechange()) { 523 | words.push('TYPECHANGE'); 524 | } 525 | if (status.isRenamed()) { 526 | words.push('RENAMED'); 527 | } 528 | if (status.isIgnored()) { 529 | words.push('IGNORED'); 530 | } 531 | 532 | return words; 533 | } 534 | 535 | function statusToText(status) { 536 | var words = []; 537 | if (status.isNew()) { 538 | words.push(kbString.build( 539 | kbString.white('['), 540 | kbString.info('NEW'), 541 | kbString.white(']') 542 | )); 543 | } 544 | if (status.isModified()) { 545 | words.push(kbString.build( 546 | kbString.white('['), 547 | kbString.warning('MODIFIED'), 548 | kbString.white(']') 549 | )); 550 | } 551 | if (status.isTypechange()) { 552 | words.push(kbString.build( 553 | kbString.white('['), 554 | kbString.warning('TYPECHANGE'), 555 | kbString.white(']') 556 | )); 557 | } 558 | if (status.isRenamed()) { 559 | words.push(kbString.build( 560 | kbString.white('['), 561 | kbString.error('RENAMED'), 562 | kbString.white(']') 563 | )); 564 | } 565 | if (status.isIgnored()) { 566 | words.push(kbString.build( 567 | kbString.white('['), 568 | kbString.white('IGNORED'), 569 | kbString.white(']') 570 | )); 571 | } 572 | 573 | return words.join(' '); 574 | } 575 | --------------------------------------------------------------------------------