├── .editorconfig ├── .eslintrc.yaml ├── .gitignore ├── .jsdoc.json ├── .npmignore ├── .travis.yml ├── CHANGELOG.md ├── LICENSE ├── README.md ├── gulpfile.babel.js ├── lib ├── Gist.js ├── GitHub.js ├── Issue.js ├── Markdown.js ├── Organization.js ├── RateLimit.js ├── Repository.js ├── Requestable.js ├── Search.js ├── Team.js └── User.js ├── mocha.opts ├── package.json ├── release.sh └── test ├── .eslintrc.yaml ├── auth.spec.js ├── dist.spec └── index.html ├── error.spec.js ├── fixtures ├── gh.png ├── gist.json ├── imageBlob.js ├── record.js ├── repos-ratelimit-exhausted.js ├── repos-ratelimit-ok.js ├── search.json └── user.json ├── gist.spec.js ├── helpers ├── callbacks.js └── getTestRepoName.js ├── issue.spec.js ├── markdown.spec.js ├── organization.spec.js ├── rate-limit.spec.js ├── repository.spec.js ├── search.spec.js ├── team.spec.js ├── user.spec.js └── vendor └── Blob.js /.editorconfig: -------------------------------------------------------------------------------- 1 | root = true 2 | 3 | [*] 4 | indent_style = space 5 | indent_size = 3 6 | end_of_line = lf 7 | charset = utf-8 8 | trim_trailing_whitespace = true 9 | insert_final_newline = true 10 | max_line_length = 120 11 | 12 | [{*.yml,package.json}] 13 | indent_size = 2 14 | 15 | [*.md] 16 | trim_trailing_whitespace = false 17 | -------------------------------------------------------------------------------- /.eslintrc.yaml: -------------------------------------------------------------------------------- 1 | --- 2 | extends: 3 | - eslint:recommended 4 | - google 5 | parserOptions: 6 | ecmaVersion: 6 7 | sourceType: module 8 | rules: 9 | arrow-parens: [error, always] 10 | eqeqeq: error 11 | guard-for-in: error 12 | indent: [error, 3, {SwitchCase: 1}] 13 | max-len: [error, {code: 120, ignoreTrailingComments: true}] 14 | no-bitwise: warn 15 | no-extend-native: error 16 | no-useless-constructor: off 17 | no-var: error 18 | padded-blocks: off 19 | quotes: [error, single, {avoidEscape: true}] 20 | require-jsdoc: 21 | - error 22 | - require: 23 | FunctionDeclaration: false 24 | ClassDeclaration: true 25 | MethodDefinition: true 26 | spaced-comment: error 27 | valid-jsdoc: [error, {requireParamDescription: true}] 28 | 29 | env: 30 | es6: true 31 | node: true 32 | browser: true 33 | # globals: 34 | # require: false 35 | # define: false 36 | # escape: false 37 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | .sass-cache/ 2 | _site/ 3 | docs/ 4 | dist/ 5 | coverage/ 6 | node_modules/ 7 | 8 | .DS_Store 9 | npm-debug.log 10 | sauce.json 11 | -------------------------------------------------------------------------------- /.jsdoc.json: -------------------------------------------------------------------------------- 1 | { 2 | "tags": { 3 | "dictionaries": ["jsdoc"] 4 | }, 5 | "source": { 6 | "include": ["lib/", "package.json", "README.md"], 7 | "includePattern": ".js$", 8 | "excludePattern": "(node_modules/|docs)" 9 | }, 10 | "plugins": [ 11 | "plugins/markdown" 12 | ], 13 | "templates": { 14 | "cleverLinks": false, 15 | "monospaceLinks": true 16 | }, 17 | "opts": { 18 | "destination": "./docs/", 19 | "encoding": "utf8", 20 | "recurse": true, 21 | "template": "./node_modules/minami" 22 | } 23 | } 24 | -------------------------------------------------------------------------------- /.npmignore: -------------------------------------------------------------------------------- 1 | docs/ 2 | coverage/ 3 | node_modules/ 4 | 5 | .DS_Store 6 | sauce.json 7 | -------------------------------------------------------------------------------- /.travis.yml: -------------------------------------------------------------------------------- 1 | sudo: false 2 | language: node_js 3 | node_js: 4 | - '6' 5 | - '5' 6 | - '4' 7 | - '0.12' 8 | 9 | cache: 10 | directories: 11 | - node_modules 12 | before_install: npm install -g npm@latest 13 | before_script: 14 | - npm run lint 15 | # - npm run build # will need this when we do sauce testing of compiled files 16 | script: 17 | - npm test 18 | # - npm run test-dist # test the compiled files 19 | # after_success: 20 | # - npm run codecov # disabled temporarialy while I work out how to generate accurate coverage of ES2015 code 21 | before_deploy: 22 | - npm run build 23 | deploy: 24 | provider: npm 25 | skip_cleanup: true 26 | on: 27 | tags: true 28 | email: clayreimann@gmail.com 29 | api_key: 30 | secure: TZHqJ9Kh2Qf0GAVDjEOQ01Ez6rGMYHKwVLOKTbnb7nSzF7iiGNT4UwzvYawm0T9p1k7X1WOqW3l7OEbIwoKl7/9azT4BBJm7qUMRfB9Zio5cL3rKubJVz7+LEEIW4iBeDWLanhUDgy9BO2JKCt8bfp/U2tltgXtu9Fm/UFPALI8= 31 | -------------------------------------------------------------------------------- /CHANGELOG.md: -------------------------------------------------------------------------------- 1 | # Change Log 2 | 3 | ## 2.4.0 - 2016/09/16 4 | ### Features 5 | * add `Issue.createLabel` 6 | * add `Repository.createKey` 7 | * add `Repository.deleteKey` 8 | * add `Repository.getBranch` 9 | * add `Repository.listKeys` 10 | * add `Repository.getKey` 11 | * add `Repository.updatePullRequest` 12 | * deprecate `Repository.updatePullRequst` 13 | 14 | ### Fixes 15 | * Request URL for deleting a hook (`Repository.deleteHook`) 16 | 17 | ## 2.3.0 - 2016/06/17 18 | ### Features 19 | * add `Repository.mergePullRequest` 20 | * add `Repository.updatePullRequest` 21 | * add `Repository.listPullRequestFiles` 22 | * add `Repository.getReadme` 23 | 24 | ## 2.2.0 - 2016/05/27 25 | ### Features 26 | * add `Issue.listIssueEvents` 27 | 28 | ### Fixes 29 | * Search returns results again 30 | 31 | ## 2.1.0 - 2016/05/26 32 | ### Features 33 | Team API 34 | * `Organization.createTeam` 35 | * `Organization.getTeams` 36 | * `Team.getTeam` 37 | * `Team.listRepos` 38 | * `Team.editTeam` 39 | * `Team.listMembers` 40 | * `Team.getMembership` 41 | * `Team.addMembership` 42 | * `Team.isManagedRepo` 43 | * `Team.manageRepo` 44 | * `Team.unmanageRepo` 45 | * `Team.deleteTeam` 46 | 47 | ## 2.0.0 48 | ### Breaking 49 | * `Repository#move` has a new argument list 50 | User 51 | * `getRepos` → `listRepos` 52 | * `getOrgs` → `listOrgs` 53 | * `getGists` → `listGists` 54 | * `getNotifications` → `listNotifications` 55 | * `getStarredRepos` → `listStarredRepos` 56 | 57 | ### Fixes 58 | * `Repository`: `move` now works 59 | * `User`: `listRepos` 60 | 61 | ## 1.2.1 62 | ### Fixes 63 | * `Repository`: Replace invalid references to `postTree` with `createTree` 64 | 65 | ## 1.2.0 - 2016/05/11 66 | ### Features 67 | * Search API now returns all pages of results 68 | * Added `Repository.listReleases` 69 | 70 | Added functions for querying organization membership 71 | * `Organization.listMembers` 72 | * `Organization.isMember` 73 | 74 | Added functions for issue comments 75 | * `Issue.listComments` 76 | * `Issue.getComment` 77 | * `Issue.editComment` 78 | * `Issue.deleteComment` 79 | 80 | ### Fixes 81 | * all functions now return a Promise 82 | 83 | ## 1.1.0 - 2016/05/03 84 | ### Features 85 | Added methods for commenting on Gists: 86 | * `Gist.listComments` 87 | * `Gist.getComment` 88 | * `Gist.editComment` 89 | * `Gist.deleteComment` 90 | * `Gist.createComment` 91 | 92 | ### Fixes 93 | * `Repository.deleteFile` now correctly returns a promise. 94 | 95 | ## 1.0.0 - 2016/04/27 96 | Complete rewrite in ES2015. 97 | 98 | * Promise-ified the API 99 | * Auto-generation of documentation 100 | * Modularized codebase 101 | * Refactored tests to run primarily in mocha 102 | 103 | ### Breaking changes 104 | Most of the breaking changes are just methods that got renamed. The changes to `User` and `Organization` are deeper 105 | changes that now scope a particular `User` or `Organization` to the entity they were instantiated with. You will need 106 | separate `User`s to query data about different user accounts. 107 | 108 | * `Github.getOrg` → `Github.getOrganization` and requires an organization name. 109 | * `Github.getUser` now requires a username. 110 | * `Issue.comment` → `Issue.createIssueComment` 111 | * `Issue.create` → `Issue.createIssue` 112 | * `Issue.edit` → `Issue.editIssue` 113 | * `Issue.get` → `Issue.getIssue` 114 | * `Issue.list` → `Issue.listIssues` 115 | * `Repository.branch` → `Repository.createBranch` 116 | * `Repository.collaborators` → `Repository.getCollaborators` 117 | * `Repository.compare` → `Repository.compareBranches` 118 | * `Repository.contents` → `Repository.getContents` and now takes an argument for the content type 119 | * `Repository.delete` has been removed. 120 | * `Repository.editHook` → `Repository.updateHook` 121 | * `Repository.editRelease` → `Repository.updateRelease` 122 | * `Repository.getCommit` no longer takes a branch as the first argument 123 | * `Repository.getPull` → `Repository.getPullRequest` 124 | * `Repository.getRef` now returns the `refspec` from GitHub's API. 125 | * `Repository.getSha` now returns the same data as GitHub's API. If the reqeusted object is not a directory then the 126 | response will contain a property `SHA`, and if the reqeusted object is a directory then the contents of the directory 127 | are `stat`ed. 128 | * `Repository.getStatuses` → `Repository.listStatuses` 129 | * `Repository.listPulls` → `Repository.listPullRequests` 130 | * `Repository.postBlob` → `Repository.createBlob` 131 | * `Repository.postTree` → `Repository.createTree` 132 | * `Repository.read` remove in favor of `Repository.getContents` 133 | * `Repository.remove` → `Repository.deleteFile` 134 | * `Repository.show` → `Repository.getDetails` 135 | * `Repository.write` → `Repository.writeFile` 136 | * `Search.code` → `Search.forCode` 137 | * `Search.issues` → `Search.forIssues` 138 | * `Search.repositories` → `Search.forRepositories` 139 | * `Search.users` → `Search.forUsers` 140 | * The Search API no longer takes a string, it now takes an object with properties `q`, `sort`, and `order` to make the 141 | parts of the query easier to grok and to better match GitHub's API. 142 | * `User.gists` → `User.getGists` 143 | * `User.notifications` → `User.getNotifications` 144 | * `User.orgRepos` → `Organization.getRepos` 145 | * `User.orgs` → `User.getOrgs` 146 | * `User.repos` → `User.getRepos` 147 | * `User.show` → `User.getProfile` and no longer takes filtering options 148 | * `User.userStarred` → `User.getStarredRepos` 149 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | Copyright (c) 2012 Michael Aufreiter, Development Seed 2 | All rights reserved. 3 | 4 | Redistribution and use in source and binary forms, with or without modification, 5 | are permitted provided that the following conditions are met: 6 | 7 | - Redistributions of source code must retain the above copyright notice, this 8 | list of conditions and the following disclaimer. 9 | - Redistributions in binary form must reproduce the above copyright notice, this 10 | list of conditions and the following disclaimer in the documentation and/or 11 | other materials provided with the distribution. 12 | - Neither the name "Development Seed" nor the names of its contributors may be 13 | used to endorse or promote products derived from this software without 14 | specific prior written permission. 15 | 16 | THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND 17 | ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED 18 | WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE 19 | DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE FOR 20 | ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES 21 | (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; 22 | LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON 23 | ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT 24 | (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS 25 | SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # Github.js 2 | 3 | [![Downloads per month](https://img.shields.io/npm/dm/github-api.svg?maxAge=2592000)][npm-package] 4 | [![Latest version](https://img.shields.io/npm/v/github-api.svg?maxAge=3600)][npm-package] 5 | [![Gitter](https://img.shields.io/gitter/room/michael/github.js.svg?maxAge=2592000)][gitter] 6 | [![Travis](https://img.shields.io/travis/michael/github.svg?maxAge=60)][travis-ci] 7 | 8 | 9 | Github.js provides a minimal higher-level wrapper around Github's API. It was concieved in the context of 10 | [Prose][prose], a content editor for GitHub. 11 | 12 | ## [Read the docs][docs] 13 | 14 | ## Installation 15 | Github.js is available from `npm` or [unpkg][unpkg]. 16 | 17 | ```shell 18 | npm install github-api 19 | ``` 20 | 21 | ```html 22 | 23 | 24 | 25 | 26 | 27 | ``` 28 | 29 | ## Compatibility 30 | Github.js is tested on Node: 31 | * 6.x 32 | * 5.x 33 | * 4.x 34 | * 0.12 35 | 36 | ## GitHub Tools 37 | 38 | The team behind Github.js has created a whole organization, called [GitHub Tools](https://github.com/github-tools), 39 | dedicated to GitHub and its API. In the near future this repository could be moved under the GitHub Tools organization 40 | as well. In the meantime, we recommend you to take a look at other projects of the organization. 41 | 42 | ## Samples 43 | 44 | ```javascript 45 | /* 46 | Data can be retrieved from the API either using callbacks (as in versions < 1.0) 47 | or using a new promise-based API. For now the promise-based API just returns the 48 | raw HTTP request promise; this might change in the next version. 49 | */ 50 | import GitHub from 'github-api'; 51 | 52 | // unauthenticated client 53 | const gh = new GitHub(); 54 | let gist = gh.getGist(); // not a gist yet 55 | gist.create({ 56 | public: true, 57 | description: 'My first gist', 58 | files: { 59 | "file1.txt": { 60 | content: "Aren't gists great!" 61 | } 62 | } 63 | }).then(function({data}) { 64 | // Promises! 65 | let gistJson = data; 66 | gist.read(function(err, gist, xhr) { 67 | // if no error occurred then err == null 68 | 69 | // gistJson === httpResponse.data 70 | 71 | // xhr === httpResponse 72 | }); 73 | }); 74 | ``` 75 | 76 | ```javascript 77 | import GitHub from 'github-api'; 78 | 79 | // basic auth 80 | const gh = new GitHub({ 81 | username: 'FOO', 82 | password: 'NotFoo' 83 | }); 84 | 85 | const me = gh.getUser(); 86 | me.listNotifications(function(err, notifications) { 87 | // do some stuff 88 | }); 89 | 90 | const clayreimann = gh.getUser('clayreimann'); 91 | clayreimann.listStarredRepos() 92 | .then(function({data: reposJson}) { 93 | // do stuff with reposJson 94 | }); 95 | ``` 96 | 97 | ```javascript 98 | var GitHub = require('github-api'); 99 | 100 | // token auth 101 | var gh = new GitHub({ 102 | token: 'MY_OAUTH_TOKEN' 103 | }); 104 | 105 | var yahoo = gh.getOrganization('yahoo'); 106 | yahoo.listRepos(function(err, repos) { 107 | // look at all the repos! 108 | }) 109 | ``` 110 | 111 | [codecov]: https://codecov.io/github/michael/github?branch=master 112 | [docs]: http://github-tools.github.io/github/ 113 | [gitter]: https://gitter.im/michael/github 114 | [npm-package]: https://www.npmjs.com/package/github-api/ 115 | [unpkg]: https://unpkg.com/github-api/ 116 | [prose]: http://prose.io 117 | [travis-ci]: https://travis-ci.org/michael/github 118 | [xhr-link]: http://blogs.msdn.com/b/ieinternals/archive/2010/05/13/xdomainrequest-restrictions-limitations-and-workarounds.aspx 119 | -------------------------------------------------------------------------------- /gulpfile.babel.js: -------------------------------------------------------------------------------- 1 | import gulp from 'gulp'; 2 | import eslint from 'gulp-eslint'; 3 | import babel from 'gulp-babel'; 4 | import rename from 'gulp-rename'; 5 | 6 | import browserify from 'browserify'; 7 | import buffer from 'vinyl-buffer'; 8 | import del from 'del'; 9 | import source from 'vinyl-source-stream'; 10 | import sourcemaps from 'gulp-sourcemaps'; 11 | import uglify from 'gulp-uglify'; 12 | 13 | const ALL_SOURCES = [ 14 | '*.js', 15 | 'lib/*.js', 16 | 'test/*.js' 17 | ]; 18 | 19 | gulp.task('lint', function() { 20 | return gulp.src(ALL_SOURCES) 21 | .pipe(eslint()) 22 | .pipe(eslint.format()) 23 | .pipe(eslint.failAfterError()) 24 | ; 25 | }); 26 | 27 | gulp.task('clean', function() { 28 | return Promise.all([del('dist/'), del('coverage/')]); 29 | }); 30 | 31 | gulp.task('build', [ 32 | 'build:bundled:min', 33 | 'build:external:min', 34 | 'build:bundled:debug', 35 | 'build:external:debug', 36 | 'build:components' 37 | ]); 38 | 39 | const bundledConfig = { 40 | debug: true, 41 | entries: 'lib/GitHub.js', 42 | standalone: 'GitHub' 43 | }; 44 | const externalConfig = { 45 | debug: true, 46 | entries: 'lib/GitHub.js', 47 | standalone: 'GitHub', 48 | external: [ 49 | 'axios', 50 | 'js-base64', 51 | 'es6-promise', 52 | 'debug', 53 | 'utf8' 54 | ], 55 | bundleExternal: false 56 | }; 57 | gulp.task('build:bundled:min', function() { 58 | return buildBundle(bundledConfig, '.bundle.min.js', true); 59 | }); 60 | gulp.task('build:external:min', function() { 61 | return buildBundle(externalConfig, '.min.js', true); 62 | }); 63 | gulp.task('build:bundled:debug', function() { 64 | return buildBundle(bundledConfig, '.bundle.js', false); 65 | }); 66 | gulp.task('build:external:debug', function() { 67 | return buildBundle(externalConfig, '.js', false); 68 | }); 69 | gulp.task('build:components', function() { 70 | return gulp.src('lib/*.js') 71 | .pipe(sourcemaps.init()) 72 | .pipe(babel()) 73 | .pipe(sourcemaps.write('.')) 74 | .pipe(gulp.dest('dist/components')) 75 | ; 76 | }); 77 | 78 | function buildBundle(options, extname, minify) { 79 | let stream = browserify(options) 80 | .transform('babelify') 81 | .bundle() 82 | .pipe(source('GitHub.js')) 83 | .pipe(buffer()) 84 | .pipe(sourcemaps.init({ 85 | loadMaps: true 86 | })); 87 | 88 | if (minify) { 89 | stream = stream.pipe(uglify()); 90 | } 91 | 92 | return stream.pipe(rename({extname})) 93 | .pipe(sourcemaps.write('.')) 94 | .pipe(gulp.dest('dist')) 95 | ; 96 | } 97 | -------------------------------------------------------------------------------- /lib/Gist.js: -------------------------------------------------------------------------------- 1 | /** 2 | * @file 3 | * @copyright 2013 Michael Aufreiter (Development Seed) and 2016 Yahoo Inc. 4 | * @license Licensed under {@link https://spdx.org/licenses/BSD-3-Clause-Clear.html BSD-3-Clause-Clear}. 5 | * Github.js is freely distributable. 6 | */ 7 | 8 | import Requestable from './Requestable'; 9 | 10 | /** 11 | * A Gist can retrieve and modify gists. 12 | */ 13 | class Gist extends Requestable { 14 | /** 15 | * Create a Gist. 16 | * @param {string} id - the id of the gist (not required when creating a gist) 17 | * @param {Requestable.auth} [auth] - information required to authenticate to Github 18 | * @param {string} [apiBase=https://api.github.com] - the base Github API URL 19 | */ 20 | constructor(id, auth, apiBase) { 21 | super(auth, apiBase); 22 | this.__id = id; 23 | } 24 | 25 | /** 26 | * Fetch a gist. 27 | * @see https://developer.github.com/v3/gists/#get-a-single-gist 28 | * @param {Requestable.callback} [cb] - will receive the gist 29 | * @return {Promise} - the Promise for the http request 30 | */ 31 | read(cb) { 32 | return this._request('GET', `/gists/${this.__id}`, null, cb); 33 | } 34 | 35 | /** 36 | * Create a new gist. 37 | * @see https://developer.github.com/v3/gists/#create-a-gist 38 | * @param {Object} gist - the data for the new gist 39 | * @param {Requestable.callback} [cb] - will receive the new gist upon creation 40 | * @return {Promise} - the Promise for the http request 41 | */ 42 | create(gist, cb) { 43 | return this._request('POST', '/gists', gist, cb) 44 | .then((response) => { 45 | this.__id = response.data.id; 46 | return response; 47 | }); 48 | } 49 | 50 | /** 51 | * Delete a gist. 52 | * @see https://developer.github.com/v3/gists/#delete-a-gist 53 | * @param {Requestable.callback} [cb] - will receive true if the request succeeds 54 | * @return {Promise} - the Promise for the http request 55 | */ 56 | delete(cb) { 57 | return this._request('DELETE', `/gists/${this.__id}`, null, cb); 58 | } 59 | 60 | /** 61 | * Fork a gist. 62 | * @see https://developer.github.com/v3/gists/#fork-a-gist 63 | * @param {Requestable.callback} [cb] - the function that will receive the gist 64 | * @return {Promise} - the Promise for the http request 65 | */ 66 | fork(cb) { 67 | return this._request('POST', `/gists/${this.__id}/forks`, null, cb); 68 | } 69 | 70 | /** 71 | * Update a gist. 72 | * @see https://developer.github.com/v3/gists/#edit-a-gist 73 | * @param {Object} gist - the new data for the gist 74 | * @param {Requestable.callback} [cb] - the function that receives the API result 75 | * @return {Promise} - the Promise for the http request 76 | */ 77 | update(gist, cb) { 78 | return this._request('PATCH', `/gists/${this.__id}`, gist, cb); 79 | } 80 | 81 | /** 82 | * Star a gist. 83 | * @see https://developer.github.com/v3/gists/#star-a-gist 84 | * @param {Requestable.callback} [cb] - will receive true if the request is successful 85 | * @return {Promise} - the Promise for the http request 86 | */ 87 | star(cb) { 88 | return this._request('PUT', `/gists/${this.__id}/star`, null, cb); 89 | } 90 | 91 | /** 92 | * Unstar a gist. 93 | * @see https://developer.github.com/v3/gists/#unstar-a-gist 94 | * @param {Requestable.callback} [cb] - will receive true if the request is successful 95 | * @return {Promise} - the Promise for the http request 96 | */ 97 | unstar(cb) { 98 | return this._request('DELETE', `/gists/${this.__id}/star`, null, cb); 99 | } 100 | 101 | /** 102 | * Check if a gist is starred by the user. 103 | * @see https://developer.github.com/v3/gists/#check-if-a-gist-is-starred 104 | * @param {Requestable.callback} [cb] - will receive true if the gist is starred and false if the gist is not starred 105 | * @return {Promise} - the Promise for the http request 106 | */ 107 | isStarred(cb) { 108 | return this._request204or404(`/gists/${this.__id}/star`, null, cb); 109 | } 110 | 111 | /** 112 | * List the gist's comments 113 | * @see https://developer.github.com/v3/gists/comments/#list-comments-on-a-gist 114 | * @param {Requestable.callback} [cb] - will receive the array of comments 115 | * @return {Promise} - the promise for the http request 116 | */ 117 | listComments(cb) { 118 | return this._requestAllPages(`/gists/${this.__id}/comments`, null, cb); 119 | } 120 | 121 | /** 122 | * Fetch one of the gist's comments 123 | * @see https://developer.github.com/v3/gists/comments/#get-a-single-comment 124 | * @param {number} comment - the id of the comment 125 | * @param {Requestable.callback} [cb] - will receive the comment 126 | * @return {Promise} - the Promise for the http request 127 | */ 128 | getComment(comment, cb) { 129 | return this._request('GET', `/gists/${this.__id}/comments/${comment}`, null, cb); 130 | } 131 | 132 | /** 133 | * Comment on a gist 134 | * @see https://developer.github.com/v3/gists/comments/#create-a-comment 135 | * @param {string} comment - the comment to add 136 | * @param {Requestable.callback} [cb] - the function that receives the API result 137 | * @return {Promise} - the Promise for the http request 138 | */ 139 | createComment(comment, cb) { 140 | return this._request('POST', `/gists/${this.__id}/comments`, {body: comment}, cb); 141 | } 142 | 143 | /** 144 | * Edit a comment on the gist 145 | * @see https://developer.github.com/v3/gists/comments/#edit-a-comment 146 | * @param {number} comment - the id of the comment 147 | * @param {string} body - the new comment 148 | * @param {Requestable.callback} [cb] - will receive the modified comment 149 | * @return {Promise} - the promise for the http request 150 | */ 151 | editComment(comment, body, cb) { 152 | return this._request('PATCH', `/gists/${this.__id}/comments/${comment}`, {body: body}, cb); 153 | } 154 | 155 | /** 156 | * Delete a comment on the gist. 157 | * @see https://developer.github.com/v3/gists/comments/#delete-a-comment 158 | * @param {number} comment - the id of the comment 159 | * @param {Requestable.callback} [cb] - will receive true if the request succeeds 160 | * @return {Promise} - the Promise for the http request 161 | */ 162 | deleteComment(comment, cb) { 163 | return this._request('DELETE', `/gists/${this.__id}/comments/${comment}`, null, cb); 164 | } 165 | } 166 | 167 | module.exports = Gist; 168 | -------------------------------------------------------------------------------- /lib/GitHub.js: -------------------------------------------------------------------------------- 1 | /** 2 | * @file 3 | * @copyright 2013 Michael Aufreiter (Development Seed) and 2016 Yahoo Inc. 4 | * @license Licensed under {@link https://spdx.org/licenses/BSD-3-Clause-Clear.html BSD-3-Clause-Clear}. 5 | * Github.js is freely distributable. 6 | */ 7 | /* eslint valid-jsdoc: ["error", {"requireReturnDescription": false}] */ 8 | 9 | import Gist from './Gist'; 10 | import User from './User'; 11 | import Issue from './Issue'; 12 | import Search from './Search'; 13 | import RateLimit from './RateLimit'; 14 | import Repository from './Repository'; 15 | import Organization from './Organization'; 16 | import Team from './Team'; 17 | import Markdown from './Markdown'; 18 | 19 | /** 20 | * GitHub encapsulates the functionality to create various API wrapper objects. 21 | */ 22 | class GitHub { 23 | /** 24 | * Create a new GitHub. 25 | * @param {Requestable.auth} [auth] - the credentials to authenticate to Github. If auth is 26 | * not provided requests will be made unauthenticated 27 | * @param {string} [apiBase=https://api.github.com] - the base Github API URL 28 | */ 29 | constructor(auth, apiBase = 'https://api.github.com') { 30 | this.__apiBase = apiBase; 31 | this.__auth = auth || {}; 32 | } 33 | 34 | /** 35 | * Create a new Gist wrapper 36 | * @param {number} [id] - the id for the gist, leave undefined when creating a new gist 37 | * @return {Gist} 38 | */ 39 | getGist(id) { 40 | return new Gist(id, this.__auth, this.__apiBase); 41 | } 42 | 43 | /** 44 | * Create a new User wrapper 45 | * @param {string} [user] - the name of the user to get information about 46 | * leave undefined for the authenticated user 47 | * @return {User} 48 | */ 49 | getUser(user) { 50 | return new User(user, this.__auth, this.__apiBase); 51 | } 52 | 53 | /** 54 | * Create a new Organization wrapper 55 | * @param {string} organization - the name of the organization 56 | * @return {Organization} 57 | */ 58 | getOrganization(organization) { 59 | return new Organization(organization, this.__auth, this.__apiBase); 60 | } 61 | 62 | /** 63 | * create a new Team wrapper 64 | * @param {string} teamId - the name of the team 65 | * @return {team} 66 | */ 67 | getTeam(teamId) { 68 | return new Team(teamId, this.__auth, this.__apiBase); 69 | } 70 | 71 | /** 72 | * Create a new Repository wrapper 73 | * @param {string} user - the user who owns the respository 74 | * @param {string} repo - the name of the repository 75 | * @return {Repository} 76 | */ 77 | getRepo(user, repo) { 78 | return new Repository(this._getFullName(user, repo), this.__auth, this.__apiBase); 79 | } 80 | 81 | /** 82 | * Create a new Issue wrapper 83 | * @param {string} user - the user who owns the respository 84 | * @param {string} repo - the name of the repository 85 | * @return {Issue} 86 | */ 87 | getIssues(user, repo) { 88 | return new Issue(this._getFullName(user, repo), this.__auth, this.__apiBase); 89 | } 90 | 91 | /** 92 | * Create a new Search wrapper 93 | * @param {string} query - the query to search for 94 | * @return {Search} 95 | */ 96 | search(query) { 97 | return new Search(query, this.__auth, this.__apiBase); 98 | } 99 | 100 | /** 101 | * Create a new RateLimit wrapper 102 | * @return {RateLimit} 103 | */ 104 | getRateLimit() { 105 | return new RateLimit(this.__auth, this.__apiBase); 106 | } 107 | 108 | /** 109 | * Create a new Markdown wrapper 110 | * @return {Markdown} 111 | */ 112 | getMarkdown() { 113 | return new Markdown(this.__auth, this.__apiBase); 114 | } 115 | 116 | /** 117 | * Computes the full repository name 118 | * @param {string} user - the username (or the full name) 119 | * @param {string} repo - the repository name, must not be passed if `user` is the full name 120 | * @return {string} the repository's full name 121 | */ 122 | _getFullName(user, repo) { 123 | let fullname = user; 124 | 125 | if (repo) { 126 | fullname = `${user}/${repo}`; 127 | } 128 | 129 | return fullname; 130 | } 131 | } 132 | 133 | module.exports = GitHub; 134 | -------------------------------------------------------------------------------- /lib/Issue.js: -------------------------------------------------------------------------------- 1 | /** 2 | * @file 3 | * @copyright 2013 Michael Aufreiter (Development Seed) and 2016 Yahoo Inc. 4 | * @license Licensed under {@link https://spdx.org/licenses/BSD-3-Clause-Clear.html BSD-3-Clause-Clear}. 5 | * Github.js is freely distributable. 6 | */ 7 | 8 | import Requestable from './Requestable'; 9 | 10 | /** 11 | * Issue wraps the functionality to get issues for repositories 12 | */ 13 | class Issue extends Requestable { 14 | /** 15 | * Create a new Issue 16 | * @param {string} repository - the full name of the repository (`:user/:repo`) to get issues for 17 | * @param {Requestable.auth} [auth] - information required to authenticate to Github 18 | * @param {string} [apiBase=https://api.github.com] - the base Github API URL 19 | */ 20 | constructor(repository, auth, apiBase) { 21 | super(auth, apiBase); 22 | this.__repository = repository; 23 | } 24 | 25 | /** 26 | * Create a new issue 27 | * @see https://developer.github.com/v3/issues/#create-an-issue 28 | * @param {Object} issueData - the issue to create 29 | * @param {Requestable.callback} [cb] - will receive the created issue 30 | * @return {Promise} - the promise for the http request 31 | */ 32 | createIssue(issueData, cb) { 33 | return this._request('POST', `/repos/${this.__repository}/issues`, issueData, cb); 34 | } 35 | 36 | /** 37 | * List the issues for the repository 38 | * @see https://developer.github.com/v3/issues/#list-issues-for-a-repository 39 | * @param {Object} options - filtering options 40 | * @param {Requestable.callback} [cb] - will receive the array of issues 41 | * @return {Promise} - the promise for the http request 42 | */ 43 | listIssues(options, cb) { 44 | return this._requestAllPages(`/repos/${this.__repository}/issues`, options, cb); 45 | } 46 | 47 | /** 48 | * List the events for an issue 49 | * @see https://developer.github.com/v3/issues/events/#list-events-for-an-issue 50 | * @param {number} issue - the issue to get events for 51 | * @param {Requestable.callback} [cb] - will receive the list of events 52 | * @return {Promise} - the promise for the http request 53 | */ 54 | listIssueEvents(issue, cb) { 55 | return this._request('GET', `/repos/${this.__repository}/issues/${issue}/events`, null, cb); 56 | } 57 | 58 | /** 59 | * List comments on an issue 60 | * @see https://developer.github.com/v3/issues/comments/#list-comments-on-an-issue 61 | * @param {number} issue - the id of the issue to get comments from 62 | * @param {Requestable.callback} [cb] - will receive the comments 63 | * @return {Promise} - the promise for the http request 64 | */ 65 | listIssueComments(issue, cb) { 66 | return this._request('GET', `/repos/${this.__repository}/issues/${issue}/comments`, null, cb); 67 | } 68 | 69 | /** 70 | * Get a single comment on an issue 71 | * @see https://developer.github.com/v3/issues/comments/#get-a-single-comment 72 | * @param {number} id - the comment id to get 73 | * @param {Requestable.callback} [cb] - will receive the comment 74 | * @return {Promise} - the promise for the http request 75 | */ 76 | getIssueComment(id, cb) { 77 | return this._request('GET', `/repos/${this.__repository}/issues/comments/${id}`, null, cb); 78 | } 79 | 80 | /** 81 | * Comment on an issue 82 | * @see https://developer.github.com/v3/issues/comments/#create-a-comment 83 | * @param {number} issue - the id of the issue to comment on 84 | * @param {string} comment - the comment to add 85 | * @param {Requestable.callback} [cb] - will receive the created comment 86 | * @return {Promise} - the promise for the http request 87 | */ 88 | createIssueComment(issue, comment, cb) { 89 | return this._request('POST', `/repos/${this.__repository}/issues/${issue}/comments`, {body: comment}, cb); 90 | } 91 | 92 | /** 93 | * Edit a comment on an issue 94 | * @see https://developer.github.com/v3/issues/comments/#edit-a-comment 95 | * @param {number} id - the comment id to edit 96 | * @param {string} comment - the comment to edit 97 | * @param {Requestable.callback} [cb] - will receive the edited comment 98 | * @return {Promise} - the promise for the http request 99 | */ 100 | editIssueComment(id, comment, cb) { 101 | return this._request('PATCH', `/repos/${this.__repository}/issues/comments/${id}`, {body: comment}, cb); 102 | } 103 | 104 | /** 105 | * Delete a comment on an issue 106 | * @see https://developer.github.com/v3/issues/comments/#delete-a-comment 107 | * @param {number} id - the comment id to delete 108 | * @param {Requestable.callback} [cb] - will receive true if the request is successful 109 | * @return {Promise} - the promise for the http request 110 | */ 111 | deleteIssueComment(id, cb) { 112 | return this._request('DELETE', `/repos/${this.__repository}/issues/comments/${id}`, null, cb); 113 | } 114 | 115 | /** 116 | * Edit an issue 117 | * @see https://developer.github.com/v3/issues/#edit-an-issue 118 | * @param {number} issue - the issue number to edit 119 | * @param {Object} issueData - the new issue data 120 | * @param {Requestable.callback} [cb] - will receive the modified issue 121 | * @return {Promise} - the promise for the http request 122 | */ 123 | editIssue(issue, issueData, cb) { 124 | return this._request('PATCH', `/repos/${this.__repository}/issues/${issue}`, issueData, cb); 125 | } 126 | 127 | /** 128 | * Get a particular issue 129 | * @see https://developer.github.com/v3/issues/#get-a-single-issue 130 | * @param {number} issue - the issue number to fetch 131 | * @param {Requestable.callback} [cb] - will receive the issue 132 | * @return {Promise} - the promise for the http request 133 | */ 134 | getIssue(issue, cb) { 135 | return this._request('GET', `/repos/${this.__repository}/issues/${issue}`, null, cb); 136 | } 137 | 138 | /** 139 | * List the milestones for the repository 140 | * @see https://developer.github.com/v3/issues/milestones/#list-milestones-for-a-repository 141 | * @param {Object} options - filtering options 142 | * @param {Requestable.callback} [cb] - will receive the array of milestones 143 | * @return {Promise} - the promise for the http request 144 | */ 145 | listMilestones(options, cb) { 146 | return this._request('GET', `/repos/${this.__repository}/milestones`, options, cb); 147 | } 148 | 149 | /** 150 | * Get a milestone 151 | * @see https://developer.github.com/v3/issues/milestones/#get-a-single-milestone 152 | * @param {string} milestone - the id of the milestone to fetch 153 | * @param {Requestable.callback} [cb] - will receive the array of milestones 154 | * @return {Promise} - the promise for the http request 155 | */ 156 | getMilestone(milestone, cb) { 157 | return this._request('GET', `/repos/${this.__repository}/milestones/${milestone}`, null, cb); 158 | } 159 | 160 | /** 161 | * Create a new milestone 162 | * @see https://developer.github.com/v3/issues/milestones/#create-a-milestone 163 | * @param {Object} milestoneData - the milestone definition 164 | * @param {Requestable.callback} [cb] - will receive the array of milestones 165 | * @return {Promise} - the promise for the http request 166 | */ 167 | createMilestone(milestoneData, cb) { 168 | return this._request('POST', `/repos/${this.__repository}/milestones`, milestoneData, cb); 169 | } 170 | 171 | /** 172 | * Edit a milestone 173 | * @see https://developer.github.com/v3/issues/milestones/#update-a-milestone 174 | * @param {string} milestone - the id of the milestone to edit 175 | * @param {Object} milestoneData - the updates to make to the milestone 176 | * @param {Requestable.callback} [cb] - will receive the array of milestones 177 | * @return {Promise} - the promise for the http request 178 | */ 179 | editMilestone(milestone, milestoneData, cb) { 180 | return this._request('PATCH', `/repos/${this.__repository}/milestones/${milestone}`, milestoneData, cb); 181 | } 182 | 183 | /** 184 | * Delete a milestone (this is distinct from closing a milestone) 185 | * @see https://developer.github.com/v3/issues/milestones/#delete-a-milestone 186 | * @param {string} milestone - the id of the milestone to delete 187 | * @param {Requestable.callback} [cb] - will receive the array of milestones 188 | * @return {Promise} - the promise for the http request 189 | */ 190 | deleteMilestone(milestone, cb) { 191 | return this._request('DELETE', `/repos/${this.__repository}/milestones/${milestone}`, null, cb); 192 | } 193 | 194 | /** 195 | * Create a new label 196 | * @see https://developer.github.com/v3/issues/labels/#create-a-label 197 | * @param {Object} labelData - the label definition 198 | * @param {Requestable.callback} [cb] - will receive the object representing the label 199 | * @return {Promise} - the promise for the http request 200 | */ 201 | createLabel(labelData, cb) { 202 | return this._request('POST', `/repos/${this.__repository}/labels`, labelData, cb); 203 | } 204 | } 205 | 206 | module.exports = Issue; 207 | -------------------------------------------------------------------------------- /lib/Markdown.js: -------------------------------------------------------------------------------- 1 | /** 2 | * @file 3 | * @copyright 2013 Michael Aufreiter (Development Seed) and 2016 Yahoo Inc. 4 | * @license Licensed under {@link https://spdx.org/licenses/BSD-3-Clause-Clear.html BSD-3-Clause-Clear}. 5 | * Github.js is freely distributable. 6 | */ 7 | 8 | import Requestable from './Requestable'; 9 | 10 | /** 11 | * RateLimit allows users to query their rate-limit status 12 | */ 13 | class Markdown extends Requestable { 14 | /** 15 | * construct a RateLimit 16 | * @param {Requestable.auth} auth - the credentials to authenticate to GitHub 17 | * @param {string} [apiBase] - the base Github API URL 18 | * @return {Promise} - the promise for the http request 19 | */ 20 | constructor(auth, apiBase) { 21 | super(auth, apiBase); 22 | } 23 | 24 | /** 25 | * Render html from Markdown text. 26 | * @see https://developer.github.com/v3/markdown/#render-an-arbitrary-markdown-document 27 | * @param {Object} options - conversion options 28 | * @param {string} [options.text] - the markdown text to convert 29 | * @param {string} [options.mode=markdown] - can be either `markdown` or `gfm` 30 | * @param {string} [options.context] - repository name if mode is gfm 31 | * @param {Requestable.callback} [cb] - will receive the converted html 32 | * @return {Promise} - the promise for the http request 33 | */ 34 | render(options, cb) { 35 | return this._request('POST', '/markdown', options, cb); 36 | } 37 | } 38 | 39 | module.exports = Markdown; 40 | -------------------------------------------------------------------------------- /lib/Organization.js: -------------------------------------------------------------------------------- 1 | /** 2 | * @file 3 | * @copyright 2013 Michael Aufreiter (Development Seed) and 2016 Yahoo Inc. 4 | * @license Licensed under {@link https://spdx.org/licenses/BSD-3-Clause-Clear.html BSD-3-Clause-Clear}. 5 | * Github.js is freely distributable. 6 | */ 7 | 8 | import Requestable from './Requestable'; 9 | 10 | /** 11 | * Organization encapsulates the functionality to create repositories in organizations 12 | */ 13 | class Organization extends Requestable { 14 | /** 15 | * Create a new Organization 16 | * @param {string} organization - the name of the organization 17 | * @param {Requestable.auth} [auth] - information required to authenticate to Github 18 | * @param {string} [apiBase=https://api.github.com] - the base Github API URL 19 | */ 20 | constructor(organization, auth, apiBase) { 21 | super(auth, apiBase); 22 | this.__name = organization; 23 | } 24 | 25 | /** 26 | * Create a repository in an organization 27 | * @see https://developer.github.com/v3/repos/#create 28 | * @param {Object} options - the repository definition 29 | * @param {Requestable.callback} [cb] - will receive the created repository 30 | * @return {Promise} - the promise for the http request 31 | */ 32 | createRepo(options, cb) { 33 | return this._request('POST', `/orgs/${this.__name}/repos`, options, cb); 34 | } 35 | 36 | /** 37 | * List the repositories in an organization 38 | * @see https://developer.github.com/v3/repos/#list-organization-repositories 39 | * @param {Requestable.callback} [cb] - will receive the list of repositories 40 | * @return {Promise} - the promise for the http request 41 | */ 42 | getRepos(cb) { 43 | let requestOptions = this._getOptionsWithDefaults({direction: 'desc'}); 44 | 45 | return this._requestAllPages(`/orgs/${this.__name}/repos`, requestOptions, cb); 46 | } 47 | 48 | /** 49 | * Query if the user is a member or not 50 | * @param {string} username - the user in question 51 | * @param {Requestable.callback} [cb] - will receive true if the user is a member 52 | * @return {Promise} - the promise for the http request 53 | */ 54 | isMember(username, cb) { 55 | return this._request204or404(`/orgs/${this.__name}/members/${username}`, null, cb); 56 | } 57 | 58 | /** 59 | * List the users who are members of the company 60 | * @see https://developer.github.com/v3/orgs/members/#members-list 61 | * @param {object} options - filtering options 62 | * @param {string} [options.filter=all] - can be either `2fa_disabled` or `all` 63 | * @param {string} [options.role=all] - can be one of: `all`, `admin`, or `member` 64 | * @param {Requestable.callback} [cb] - will receive the list of users 65 | * @return {Promise} - the promise for the http request 66 | */ 67 | listMembers(options, cb) { 68 | return this._request('GET', `/orgs/${this.__name}/members`, options, cb); 69 | } 70 | 71 | /** 72 | * List the Teams in the Organization 73 | * @see https://developer.github.com/v3/orgs/teams/#list-teams 74 | * @param {Requestable.callback} [cb] - will receive the list of teams 75 | * @return {Promise} - the promise for the http request 76 | */ 77 | getTeams(cb) { 78 | return this._requestAllPages(`/orgs/${this.__name}/teams`, undefined, cb); 79 | } 80 | 81 | /** 82 | * Create a team 83 | * @see https://developer.github.com/v3/orgs/teams/#create-team 84 | * @param {object} options - Team creation parameters 85 | * @param {string} options.name - The name of the team 86 | * @param {string} [options.description] - Team description 87 | * @param {string} [options.repo_names] - Repos to add the team to 88 | * @param {string} [options.privacy=secret] - The level of privacy the team should have. Can be either one 89 | * of: `secret`, or `closed` 90 | * @param {Requestable.callback} [cb] - will receive the created team 91 | * @return {Promise} - the promise for the http request 92 | */ 93 | createTeam(options, cb) { 94 | return this._request('POST', `/orgs/${this.__name}/teams`, options, cb); 95 | } 96 | } 97 | 98 | module.exports = Organization; 99 | -------------------------------------------------------------------------------- /lib/RateLimit.js: -------------------------------------------------------------------------------- 1 | /** 2 | * @file 3 | * @copyright 2013 Michael Aufreiter (Development Seed) and 2016 Yahoo Inc. 4 | * @license Licensed under {@link https://spdx.org/licenses/BSD-3-Clause-Clear.html BSD-3-Clause-Clear}. 5 | * Github.js is freely distributable. 6 | */ 7 | 8 | import Requestable from './Requestable'; 9 | 10 | /** 11 | * RateLimit allows users to query their rate-limit status 12 | */ 13 | class RateLimit extends Requestable { 14 | /** 15 | * construct a RateLimit 16 | * @param {Requestable.auth} auth - the credentials to authenticate to GitHub 17 | * @param {string} [apiBase] - the base Github API URL 18 | * @return {Promise} - the promise for the http request 19 | */ 20 | constructor(auth, apiBase) { 21 | super(auth, apiBase); 22 | } 23 | 24 | /** 25 | * Query the current rate limit 26 | * @see https://developer.github.com/v3/rate_limit/ 27 | * @param {Requestable.callback} [cb] - will receive the rate-limit data 28 | * @return {Promise} - the promise for the http request 29 | */ 30 | getRateLimit(cb) { 31 | return this._request('GET', '/rate_limit', null, cb); 32 | } 33 | } 34 | 35 | module.exports = RateLimit; 36 | -------------------------------------------------------------------------------- /lib/Repository.js: -------------------------------------------------------------------------------- 1 | /** 2 | * @file 3 | * @copyright 2013 Michael Aufreiter (Development Seed) and 2016 Yahoo Inc. 4 | * @license Licensed under {@link https://spdx.org/licenses/BSD-3-Clause-Clear.html BSD-3-Clause-Clear}. 5 | * Github.js is freely distributable. 6 | */ 7 | 8 | import Requestable from './Requestable'; 9 | import Utf8 from 'utf8'; 10 | import { 11 | Base64 12 | } from 'js-base64'; 13 | import debug from 'debug'; 14 | const log = debug('github:repository'); 15 | 16 | /** 17 | * Respository encapsulates the functionality to create, query, and modify files. 18 | */ 19 | class Repository extends Requestable { 20 | /** 21 | * Create a Repository. 22 | * @param {string} fullname - the full name of the repository 23 | * @param {Requestable.auth} [auth] - information required to authenticate to Github 24 | * @param {string} [apiBase=https://api.github.com] - the base Github API URL 25 | */ 26 | constructor(fullname, auth, apiBase) { 27 | super(auth, apiBase); 28 | this.__fullname = fullname; 29 | this.__currentTree = { 30 | branch: null, 31 | sha: null 32 | }; 33 | } 34 | 35 | /** 36 | * Get a reference 37 | * @see https://developer.github.com/v3/git/refs/#get-a-reference 38 | * @param {string} ref - the reference to get 39 | * @param {Requestable.callback} [cb] - will receive the reference's refSpec or a list of refSpecs that match `ref` 40 | * @return {Promise} - the promise for the http request 41 | */ 42 | getRef(ref, cb) { 43 | return this._request('GET', `/repos/${this.__fullname}/git/refs/${ref}`, null, cb); 44 | } 45 | 46 | /** 47 | * Create a reference 48 | * @see https://developer.github.com/v3/git/refs/#create-a-reference 49 | * @param {Object} options - the object describing the ref 50 | * @param {Requestable.callback} [cb] - will receive the ref 51 | * @return {Promise} - the promise for the http request 52 | */ 53 | createRef(options, cb) { 54 | return this._request('POST', `/repos/${this.__fullname}/git/refs`, options, cb); 55 | } 56 | 57 | /** 58 | * Delete a reference 59 | * @see https://developer.github.com/v3/git/refs/#delete-a-reference 60 | * @param {string} ref - the name of the ref to delte 61 | * @param {Requestable.callback} [cb] - will receive true if the request is successful 62 | * @return {Promise} - the promise for the http request 63 | */ 64 | deleteRef(ref, cb) { 65 | return this._request('DELETE', `/repos/${this.__fullname}/git/refs/${ref}`, null, cb); 66 | } 67 | 68 | /** 69 | * Delete a repository 70 | * @see https://developer.github.com/v3/repos/#delete-a-repository 71 | * @param {Requestable.callback} [cb] - will receive true if the request is successful 72 | * @return {Promise} - the promise for the http request 73 | */ 74 | deleteRepo(cb) { 75 | return this._request('DELETE', `/repos/${this.__fullname}`, null, cb); 76 | } 77 | 78 | /** 79 | * List the tags on a repository 80 | * @see https://developer.github.com/v3/repos/#list-tags 81 | * @param {Requestable.callback} [cb] - will receive the tag data 82 | * @return {Promise} - the promise for the http request 83 | */ 84 | listTags(cb) { 85 | return this._request('GET', `/repos/${this.__fullname}/tags`, null, cb); 86 | } 87 | 88 | /** 89 | * List the open pull requests on the repository 90 | * @see https://developer.github.com/v3/pulls/#list-pull-requests 91 | * @param {Object} options - options to filter the search 92 | * @param {Requestable.callback} [cb] - will receive the list of PRs 93 | * @return {Promise} - the promise for the http request 94 | */ 95 | listPullRequests(options, cb) { 96 | options = options || {}; 97 | return this._request('GET', `/repos/${this.__fullname}/pulls`, options, cb); 98 | } 99 | 100 | /** 101 | * Get information about a specific pull request 102 | * @see https://developer.github.com/v3/pulls/#get-a-single-pull-request 103 | * @param {number} number - the PR you wish to fetch 104 | * @param {Requestable.callback} [cb] - will receive the PR from the API 105 | * @return {Promise} - the promise for the http request 106 | */ 107 | getPullRequest(number, cb) { 108 | return this._request('GET', `/repos/${this.__fullname}/pulls/${number}`, null, cb); 109 | } 110 | 111 | /** 112 | * List the files of a specific pull request 113 | * @see https://developer.github.com/v3/pulls/#list-pull-requests-files 114 | * @param {number|string} number - the PR you wish to fetch 115 | * @param {Requestable.callback} [cb] - will receive the list of files from the API 116 | * @return {Promise} - the promise for the http request 117 | */ 118 | listPullRequestFiles(number, cb) { 119 | return this._request('GET', `/repos/${this.__fullname}/pulls/${number}/files`, null, cb); 120 | } 121 | 122 | /** 123 | * Compare two branches/commits/repositories 124 | * @see https://developer.github.com/v3/repos/commits/#compare-two-commits 125 | * @param {string} base - the base commit 126 | * @param {string} head - the head commit 127 | * @param {Requestable.callback} cb - will receive the comparison 128 | * @return {Promise} - the promise for the http request 129 | */ 130 | compareBranches(base, head, cb) { 131 | return this._request('GET', `/repos/${this.__fullname}/compare/${base}...${head}`, null, cb); 132 | } 133 | 134 | /** 135 | * List all the branches for the repository 136 | * @see https://developer.github.com/v3/repos/#list-branches 137 | * @param {Requestable.callback} cb - will receive the list of branches 138 | * @return {Promise} - the promise for the http request 139 | */ 140 | listBranches(cb) { 141 | return this._request('GET', `/repos/${this.__fullname}/branches`, null, cb); 142 | } 143 | 144 | /** 145 | * Get a raw blob from the repository 146 | * @see https://developer.github.com/v3/git/blobs/#get-a-blob 147 | * @param {string} sha - the sha of the blob to fetch 148 | * @param {Requestable.callback} cb - will receive the blob from the API 149 | * @return {Promise} - the promise for the http request 150 | */ 151 | getBlob(sha, cb) { 152 | return this._request('GET', `/repos/${this.__fullname}/git/blobs/${sha}`, null, cb, 'raw'); 153 | } 154 | 155 | /** 156 | * Get a single branch 157 | * @see https://developer.github.com/v3/repos/branches/#get-branch 158 | * @param {string} branch - the name of the branch to fetch 159 | * @param {Requestable.callback} cb - will receive the branch from the API 160 | * @returns {Promise} - the promise for the http request 161 | */ 162 | getBranch(branch, cb) { 163 | return this._request('GET', `/repos/${this.__fullname}/branches/${branch}`, null, cb); 164 | } 165 | 166 | /** 167 | * Get a commit from the repository 168 | * @see https://developer.github.com/v3/repos/commits/#get-a-single-commit 169 | * @param {string} sha - the sha for the commit to fetch 170 | * @param {Requestable.callback} cb - will receive the commit data 171 | * @return {Promise} - the promise for the http request 172 | */ 173 | getCommit(sha, cb) { 174 | return this._request('GET', `/repos/${this.__fullname}/git/commits/${sha}`, null, cb); 175 | } 176 | 177 | /** 178 | * List the commits on a repository, optionally filtering by path, author or time range 179 | * @see https://developer.github.com/v3/repos/commits/#list-commits-on-a-repository 180 | * @param {Object} [options] - the filtering options for commits 181 | * @param {string} [options.sha] - the SHA or branch to start from 182 | * @param {string} [options.path] - the path to search on 183 | * @param {string} [options.author] - the commit author 184 | * @param {(Date|string)} [options.since] - only commits after this date will be returned 185 | * @param {(Date|string)} [options.until] - only commits before this date will be returned 186 | * @param {Requestable.callback} cb - will receive the list of commits found matching the criteria 187 | * @return {Promise} - the promise for the http request 188 | */ 189 | listCommits(options, cb) { 190 | options = options || {}; 191 | 192 | options.since = this._dateToISO(options.since); 193 | options.until = this._dateToISO(options.until); 194 | 195 | return this._request('GET', `/repos/${this.__fullname}/commits`, options, cb); 196 | } 197 | 198 | /** 199 | * Gets a single commit information for a repository 200 | * @see https://developer.github.com/v3/repos/commits/#get-a-single-commit 201 | * @param {string} ref - the reference for the commit-ish 202 | * @param {Requestable.callback} cb - will receive the commit information 203 | * @return {Promise} - the promise for the http request 204 | */ 205 | getSingleCommit(ref, cb) { 206 | ref = ref || ''; 207 | return this._request('GET', `/repos/${this.__fullname}/commits/${ref}`, null, cb); 208 | } 209 | 210 | /** 211 | * Get tha sha for a particular object in the repository. This is a convenience function 212 | * @see https://developer.github.com/v3/repos/contents/#get-contents 213 | * @param {string} [branch] - the branch to look in, or the repository's default branch if omitted 214 | * @param {string} path - the path of the file or directory 215 | * @param {Requestable.callback} cb - will receive a description of the requested object, including a `SHA` property 216 | * @return {Promise} - the promise for the http request 217 | */ 218 | getSha(branch, path, cb) { 219 | branch = branch ? `?ref=${branch}` : ''; 220 | return this._request('GET', `/repos/${this.__fullname}/contents/${path}${branch}`, null, cb); 221 | } 222 | 223 | /** 224 | * List the commit statuses for a particular sha, branch, or tag 225 | * @see https://developer.github.com/v3/repos/statuses/#list-statuses-for-a-specific-ref 226 | * @param {string} sha - the sha, branch, or tag to get statuses for 227 | * @param {Requestable.callback} cb - will receive the list of statuses 228 | * @return {Promise} - the promise for the http request 229 | */ 230 | listStatuses(sha, cb) { 231 | return this._request('GET', `/repos/${this.__fullname}/commits/${sha}/statuses`, null, cb); 232 | } 233 | 234 | /** 235 | * Get a description of a git tree 236 | * @see https://developer.github.com/v3/git/trees/#get-a-tree 237 | * @param {string} treeSHA - the SHA of the tree to fetch 238 | * @param {Requestable.callback} cb - will receive the callback data 239 | * @return {Promise} - the promise for the http request 240 | */ 241 | getTree(treeSHA, cb) { 242 | return this._request('GET', `/repos/${this.__fullname}/git/trees/${treeSHA}`, null, cb); 243 | } 244 | 245 | /** 246 | * Create a blob 247 | * @see https://developer.github.com/v3/git/blobs/#create-a-blob 248 | * @param {(string|Buffer|Blob)} content - the content to add to the repository 249 | * @param {Requestable.callback} cb - will receive the details of the created blob 250 | * @return {Promise} - the promise for the http request 251 | */ 252 | createBlob(content, cb) { 253 | let postBody = this._getContentObject(content); 254 | 255 | log('sending content', postBody); 256 | return this._request('POST', `/repos/${this.__fullname}/git/blobs`, postBody, cb); 257 | } 258 | 259 | /** 260 | * Get the object that represents the provided content 261 | * @param {string|Buffer|Blob} content - the content to send to the server 262 | * @return {Object} the representation of `content` for the GitHub API 263 | */ 264 | _getContentObject(content) { 265 | if (typeof content === 'string') { 266 | log('contet is a string'); 267 | return { 268 | content: Utf8.encode(content), 269 | encoding: 'utf-8' 270 | }; 271 | 272 | } else if (typeof Buffer !== 'undefined' && content instanceof Buffer) { 273 | log('We appear to be in Node'); 274 | return { 275 | content: content.toString('base64'), 276 | encoding: 'base64' 277 | }; 278 | 279 | } else if (typeof Blob !== 'undefined' && content instanceof Blob) { 280 | log('We appear to be in the browser'); 281 | return { 282 | content: Base64.encode(content), 283 | encoding: 'base64' 284 | }; 285 | 286 | } else { // eslint-disable-line 287 | log(`Not sure what this content is: ${typeof content}, ${JSON.stringify(content)}`); 288 | throw new Error('Unknown content passed to postBlob. Must be string or Buffer (node) or Blob (web)'); 289 | } 290 | } 291 | 292 | /** 293 | * Update a tree in Git 294 | * @see https://developer.github.com/v3/git/trees/#create-a-tree 295 | * @param {string} baseTreeSHA - the SHA of the tree to update 296 | * @param {string} path - the path for the new file 297 | * @param {string} blobSHA - the SHA for the blob to put at `path` 298 | * @param {Requestable.callback} cb - will receive the new tree that is created 299 | * @return {Promise} - the promise for the http request 300 | * @deprecated use {@link Repository#createTree} instead 301 | */ 302 | updateTree(baseTreeSHA, path, blobSHA, cb) { 303 | let newTree = { 304 | base_tree: baseTreeSHA, // eslint-disable-line 305 | tree: [{ 306 | path: path, 307 | sha: blobSHA, 308 | mode: '100644', 309 | type: 'blob' 310 | }] 311 | }; 312 | 313 | return this._request('POST', `/repos/${this.__fullname}/git/trees`, newTree, cb); 314 | } 315 | 316 | /** 317 | * Create a new tree in git 318 | * @see https://developer.github.com/v3/git/trees/#create-a-tree 319 | * @param {Object} tree - the tree to create 320 | * @param {string} baseSHA - the root sha of the tree 321 | * @param {Requestable.callback} cb - will receive the new tree that is created 322 | * @return {Promise} - the promise for the http request 323 | */ 324 | createTree(tree, baseSHA, cb) { 325 | return this._request('POST', `/repos/${this.__fullname}/git/trees`, { 326 | tree, 327 | base_tree: baseSHA // eslint-disable-line 328 | }, cb); 329 | } 330 | 331 | /** 332 | * Add a commit to the repository 333 | * @see https://developer.github.com/v3/git/commits/#create-a-commit 334 | * @param {string} parent - the SHA of the parent commit 335 | * @param {string} tree - the SHA of the tree for this commit 336 | * @param {string} message - the commit message 337 | * @param {Requestable.callback} cb - will receive the commit that is created 338 | * @return {Promise} - the promise for the http request 339 | */ 340 | commit(parent, tree, message, cb) { 341 | let data = { 342 | message, 343 | tree, 344 | parents: [parent] 345 | }; 346 | 347 | return this._request('POST', `/repos/${this.__fullname}/git/commits`, data, cb) 348 | .then((response) => { 349 | this.__currentTree.sha = response.data.sha; // Update latest commit 350 | return response; 351 | }); 352 | } 353 | 354 | /** 355 | * Update a ref 356 | * @see https://developer.github.com/v3/git/refs/#update-a-reference 357 | * @param {string} ref - the ref to update 358 | * @param {string} commitSHA - the SHA to point the reference to 359 | * @param {boolean} force - indicates whether to force or ensure a fast-forward update 360 | * @param {Requestable.callback} cb - will receive the updated ref back 361 | * @return {Promise} - the promise for the http request 362 | */ 363 | updateHead(ref, commitSHA, force, cb) { 364 | return this._request('PATCH', `/repos/${this.__fullname}/git/refs/${ref}`, { 365 | sha: commitSHA, 366 | force: force 367 | }, cb); 368 | } 369 | 370 | /** 371 | * Get information about the repository 372 | * @see https://developer.github.com/v3/repos/#get 373 | * @param {Requestable.callback} cb - will receive the information about the repository 374 | * @return {Promise} - the promise for the http request 375 | */ 376 | getDetails(cb) { 377 | return this._request('GET', `/repos/${this.__fullname}`, null, cb); 378 | } 379 | 380 | /** 381 | * List the contributors to the repository 382 | * @see https://developer.github.com/v3/repos/#list-contributors 383 | * @param {Requestable.callback} cb - will receive the list of contributors 384 | * @return {Promise} - the promise for the http request 385 | */ 386 | getContributors(cb) { 387 | return this._request('GET', `/repos/${this.__fullname}/stats/contributors`, null, cb); 388 | } 389 | 390 | /** 391 | * List the users who are collaborators on the repository. The currently authenticated user must have 392 | * push access to use this method 393 | * @see https://developer.github.com/v3/repos/collaborators/#list-collaborators 394 | * @param {Requestable.callback} cb - will receive the list of collaborators 395 | * @return {Promise} - the promise for the http request 396 | */ 397 | getCollaborators(cb) { 398 | return this._request('GET', `/repos/${this.__fullname}/collaborators`, null, cb); 399 | } 400 | 401 | /** 402 | * Check if a user is a collaborator on the repository 403 | * @see https://developer.github.com/v3/repos/collaborators/#check-if-a-user-is-a-collaborator 404 | * @param {string} username - the user to check 405 | * @param {Requestable.callback} cb - will receive true if the user is a collaborator and false if they are not 406 | * @return {Promise} - the promise for the http request {Boolean} [description] 407 | */ 408 | isCollaborator(username, cb) { 409 | return this._request('GET', `/repos/${this.__fullname}/collaborators/${username}`, null, cb); 410 | } 411 | 412 | /** 413 | * Get the contents of a repository 414 | * @see https://developer.github.com/v3/repos/contents/#get-contents 415 | * @param {string} ref - the ref to check 416 | * @param {string} path - the path containing the content to fetch 417 | * @param {boolean} raw - `true` if the results should be returned raw instead of GitHub's normalized format 418 | * @param {Requestable.callback} cb - will receive the fetched data 419 | * @return {Promise} - the promise for the http request 420 | */ 421 | getContents(ref, path, raw, cb) { 422 | path = path ? `${encodeURI(path)}` : ''; 423 | return this._request('GET', `/repos/${this.__fullname}/contents/${path}`, { 424 | ref 425 | }, cb, raw); 426 | } 427 | 428 | /** 429 | * Get the README of a repository 430 | * @see https://developer.github.com/v3/repos/contents/#get-the-readme 431 | * @param {string} ref - the ref to check 432 | * @param {boolean} raw - `true` if the results should be returned raw instead of GitHub's normalized format 433 | * @param {Requestable.callback} cb - will receive the fetched data 434 | * @return {Promise} - the promise for the http request 435 | */ 436 | getReadme(ref, raw, cb) { 437 | return this._request('GET', `/repos/${this.__fullname}/readme`, { 438 | ref 439 | }, cb, raw); 440 | } 441 | 442 | /** 443 | * Fork a repository 444 | * @see https://developer.github.com/v3/repos/forks/#create-a-fork 445 | * @param {Requestable.callback} cb - will receive the information about the newly created fork 446 | * @return {Promise} - the promise for the http request 447 | */ 448 | fork(cb) { 449 | return this._request('POST', `/repos/${this.__fullname}/forks`, null, cb); 450 | } 451 | 452 | /** 453 | * List a repository's forks 454 | * @see https://developer.github.com/v3/repos/forks/#list-forks 455 | * @param {Requestable.callback} cb - will receive the list of repositories forked from this one 456 | * @return {Promise} - the promise for the http request 457 | */ 458 | listForks(cb) { 459 | return this._request('GET', `/repos/${this.__fullname}/forks`, null, cb); 460 | } 461 | 462 | /** 463 | * Create a new branch from an existing branch. 464 | * @param {string} [oldBranch=master] - the name of the existing branch 465 | * @param {string} newBranch - the name of the new branch 466 | * @param {Requestable.callback} cb - will receive the commit data for the head of the new branch 467 | * @return {Promise} - the promise for the http request 468 | */ 469 | createBranch(oldBranch, newBranch, cb) { 470 | if (typeof newBranch === 'function') { 471 | cb = newBranch; 472 | newBranch = oldBranch; 473 | oldBranch = 'master'; 474 | } 475 | 476 | return this.getRef(`heads/${oldBranch}`) 477 | .then((response) => { 478 | let sha = response.data.object.sha; 479 | return this.createRef({ 480 | sha, 481 | ref: `refs/heads/${newBranch}` 482 | }, cb); 483 | }); 484 | } 485 | 486 | /** 487 | * Create a new pull request 488 | * @see https://developer.github.com/v3/pulls/#create-a-pull-request 489 | * @param {Object} options - the pull request description 490 | * @param {Requestable.callback} cb - will receive the new pull request 491 | * @return {Promise} - the promise for the http request 492 | */ 493 | createPullRequest(options, cb) { 494 | return this._request('POST', `/repos/${this.__fullname}/pulls`, options, cb); 495 | } 496 | 497 | /** 498 | * Update a pull request 499 | * @deprecated since version 2.4.0 500 | * @see https://developer.github.com/v3/pulls/#update-a-pull-request 501 | * @param {number|string} number - the number of the pull request to update 502 | * @param {Object} options - the pull request description 503 | * @param {Requestable.callback} [cb] - will receive the pull request information 504 | * @return {Promise} - the promise for the http request 505 | */ 506 | updatePullRequst(number, options, cb) { 507 | log('Deprecated: This method contains a typo and it has been deprecated. It will be removed in next major version. Use updatePullRequest() instead.'); 508 | 509 | return this.updatePullRequest(number, options, cb); 510 | } 511 | 512 | /** 513 | * Update a pull request 514 | * @see https://developer.github.com/v3/pulls/#update-a-pull-request 515 | * @param {number|string} number - the number of the pull request to update 516 | * @param {Object} options - the pull request description 517 | * @param {Requestable.callback} [cb] - will receive the pull request information 518 | * @return {Promise} - the promise for the http request 519 | */ 520 | updatePullRequest(number, options, cb) { 521 | return this._request('PATCH', `/repos/${this.__fullname}/pulls/${number}`, options, cb); 522 | } 523 | 524 | /** 525 | * List the hooks for the repository 526 | * @see https://developer.github.com/v3/repos/hooks/#list-hooks 527 | * @param {Requestable.callback} cb - will receive the list of hooks 528 | * @return {Promise} - the promise for the http request 529 | */ 530 | listHooks(cb) { 531 | return this._request('GET', `/repos/${this.__fullname}/hooks`, null, cb); 532 | } 533 | 534 | /** 535 | * Get a hook for the repository 536 | * @see https://developer.github.com/v3/repos/hooks/#get-single-hook 537 | * @param {number} id - the id of the webook 538 | * @param {Requestable.callback} cb - will receive the details of the webook 539 | * @return {Promise} - the promise for the http request 540 | */ 541 | getHook(id, cb) { 542 | return this._request('GET', `/repos/${this.__fullname}/hooks/${id}`, null, cb); 543 | } 544 | 545 | /** 546 | * Add a new hook to the repository 547 | * @see https://developer.github.com/v3/repos/hooks/#create-a-hook 548 | * @param {Object} options - the configuration describing the new hook 549 | * @param {Requestable.callback} cb - will receive the new webhook 550 | * @return {Promise} - the promise for the http request 551 | */ 552 | createHook(options, cb) { 553 | return this._request('POST', `/repos/${this.__fullname}/hooks`, options, cb); 554 | } 555 | 556 | /** 557 | * Edit an existing webhook 558 | * @see https://developer.github.com/v3/repos/hooks/#edit-a-hook 559 | * @param {number} id - the id of the webhook 560 | * @param {Object} options - the new description of the webhook 561 | * @param {Requestable.callback} cb - will receive the updated webhook 562 | * @return {Promise} - the promise for the http request 563 | */ 564 | updateHook(id, options, cb) { 565 | return this._request('PATCH', `/repos/${this.__fullname}/hooks/${id}`, options, cb); 566 | } 567 | 568 | /** 569 | * Delete a webhook 570 | * @see https://developer.github.com/v3/repos/hooks/#delete-a-hook 571 | * @param {number} id - the id of the webhook to be deleted 572 | * @param {Requestable.callback} cb - will receive true if the call is successful 573 | * @return {Promise} - the promise for the http request 574 | */ 575 | deleteHook(id, cb) { 576 | return this._request('DELETE', `${this.__fullname}/hooks/${id}`, null, cb); 577 | } 578 | 579 | /** 580 | * List the deploy keys for the repository 581 | * @see https://developer.github.com/v3/repos/keys/#list-deploy-keys 582 | * @param {Requestable.callback} cb - will receive the list of deploy keys 583 | * @return {Promise} - the promise for the http request 584 | */ 585 | listKeys(cb) { 586 | return this._request('GET', `/repos/${this.__fullname}/keys`, null, cb); 587 | } 588 | 589 | /** 590 | * Get a deploy key for the repository 591 | * @see https://developer.github.com/v3/repos/keys/#get-a-deploy-key 592 | * @param {number} id - the id of the deploy key 593 | * @param {Requestable.callback} cb - will receive the details of the deploy key 594 | * @return {Promise} - the promise for the http request 595 | */ 596 | getKey(id, cb) { 597 | return this._request('GET', `/repos/${this.__fullname}/keys/${id}`, null, cb); 598 | } 599 | 600 | /** 601 | * Add a new deploy key to the repository 602 | * @see https://developer.github.com/v3/repos/keys/#add-a-new-deploy-key 603 | * @param {Object} options - the configuration describing the new deploy key 604 | * @param {Requestable.callback} cb - will receive the new deploy key 605 | * @return {Promise} - the promise for the http request 606 | */ 607 | createKey(options, cb) { 608 | return this._request('POST', `/repos/${this.__fullname}/keys`, options, cb); 609 | } 610 | 611 | /** 612 | * Delete a deploy key 613 | * @see https://developer.github.com/v3/repos/keys/#remove-a-deploy-key 614 | * @param {number} id - the id of the deploy key to be deleted 615 | * @param {Requestable.callback} cb - will receive true if the call is successful 616 | * @return {Promise} - the promise for the http request 617 | */ 618 | deleteKey(id, cb) { 619 | return this._request('DELETE', `/repos/${this.__fullname}/keys/${id}`, null, cb); 620 | } 621 | 622 | /** 623 | * Delete a file from a branch 624 | * @see https://developer.github.com/v3/repos/contents/#delete-a-file 625 | * @param {string} branch - the branch to delete from, or the default branch if not specified 626 | * @param {string} path - the path of the file to remove 627 | * @param {Requestable.callback} cb - will receive the commit in which the delete occurred 628 | * @return {Promise} - the promise for the http request 629 | */ 630 | deleteFile(branch, path, cb) { 631 | return this.getSha(branch, path) 632 | .then((response) => { 633 | const deleteCommit = { 634 | message: `Delete the file at '${path}'`, 635 | sha: response.data.sha, 636 | branch 637 | }; 638 | return this._request('DELETE', `/repos/${this.__fullname}/contents/${path}`, deleteCommit, cb); 639 | }); 640 | } 641 | 642 | /** 643 | * Change all references in a repo from oldPath to new_path 644 | * @param {string} branch - the branch to carry out the reference change, or the default branch if not specified 645 | * @param {string} oldPath - original path 646 | * @param {string} newPath - new reference path 647 | * @param {Requestable.callback} cb - will receive the commit in which the move occurred 648 | * @return {Promise} - the promise for the http request 649 | */ 650 | move(branch, oldPath, newPath, cb) { 651 | let oldSha; 652 | return this.getRef(`heads/${branch}`) 653 | .then(({data: {object}}) => this.getTree(`${object.sha}?recursive=true`)) 654 | .then(({data: {tree, sha}}) => { 655 | oldSha = sha; 656 | let newTree = tree.map((ref) => { 657 | if (ref.path === oldPath) { 658 | ref.path = newPath; 659 | } 660 | if (ref.type === 'tree') { 661 | delete ref.sha; 662 | } 663 | return ref; 664 | }); 665 | return this.createTree(newTree); 666 | }) 667 | .then(({data: tree}) => this.commit(oldSha, tree.sha, `Renamed '${oldPath}' to '${newPath}'`)) 668 | .then(({data: commit}) => this.updateHead(`heads/${branch}`, commit.sha, true, cb)); 669 | } 670 | 671 | /** 672 | * Write a file to the repository 673 | * @see https://developer.github.com/v3/repos/contents/#update-a-file 674 | * @param {string} branch - the name of the branch 675 | * @param {string} path - the path for the file 676 | * @param {string} content - the contents of the file 677 | * @param {string} message - the commit message 678 | * @param {Object} [options] - commit options 679 | * @param {Object} [options.author] - the author of the commit 680 | * @param {Object} [options.commiter] - the committer 681 | * @param {boolean} [options.encode] - true if the content should be base64 encoded 682 | * @param {Requestable.callback} cb - will receive the new commit 683 | * @return {Promise} - the promise for the http request 684 | */ 685 | writeFile(branch, path, content, message, options, cb) { 686 | if (typeof options === 'function') { 687 | cb = options; 688 | options = {}; 689 | } 690 | let filePath = path ? encodeURI(path) : ''; 691 | let shouldEncode = options.encode !== false; 692 | let commit = { 693 | branch, 694 | message, 695 | author: options.author, 696 | committer: options.committer, 697 | content: shouldEncode ? Base64.encode(content) : content 698 | }; 699 | 700 | return this.getSha(branch, filePath) 701 | .then((response) => { 702 | commit.sha = response.data.sha; 703 | return this._request('PUT', `/repos/${this.__fullname}/contents/${filePath}`, commit, cb); 704 | }, () => { 705 | return this._request('PUT', `/repos/${this.__fullname}/contents/${filePath}`, commit, cb); 706 | }); 707 | } 708 | 709 | /** 710 | * Check if a repository is starred by you 711 | * @see https://developer.github.com/v3/activity/starring/#check-if-you-are-starring-a-repository 712 | * @param {Requestable.callback} cb - will receive true if the repository is starred and false if the repository 713 | * is not starred 714 | * @return {Promise} - the promise for the http request {Boolean} [description] 715 | */ 716 | isStarred(cb) { 717 | return this._request204or404(`/user/starred/${this.__fullname}`, null, cb); 718 | } 719 | 720 | /** 721 | * Star a repository 722 | * @see https://developer.github.com/v3/activity/starring/#star-a-repository 723 | * @param {Requestable.callback} cb - will receive true if the repository is starred 724 | * @return {Promise} - the promise for the http request 725 | */ 726 | star(cb) { 727 | return this._request('PUT', `/user/starred/${this.__fullname}`, null, cb); 728 | } 729 | 730 | /** 731 | * Unstar a repository 732 | * @see https://developer.github.com/v3/activity/starring/#unstar-a-repository 733 | * @param {Requestable.callback} cb - will receive true if the repository is unstarred 734 | * @return {Promise} - the promise for the http request 735 | */ 736 | unstar(cb) { 737 | return this._request('DELETE', `/user/starred/${this.__fullname}`, null, cb); 738 | } 739 | 740 | /** 741 | * Create a new release 742 | * @see https://developer.github.com/v3/repos/releases/#create-a-release 743 | * @param {Object} options - the description of the release 744 | * @param {Requestable.callback} cb - will receive the newly created release 745 | * @return {Promise} - the promise for the http request 746 | */ 747 | createRelease(options, cb) { 748 | return this._request('POST', `/repos/${this.__fullname}/releases`, options, cb); 749 | } 750 | 751 | /** 752 | * Edit a release 753 | * @see https://developer.github.com/v3/repos/releases/#edit-a-release 754 | * @param {string} id - the id of the release 755 | * @param {Object} options - the description of the release 756 | * @param {Requestable.callback} cb - will receive the modified release 757 | * @return {Promise} - the promise for the http request 758 | */ 759 | updateRelease(id, options, cb) { 760 | return this._request('PATCH', `/repos/${this.__fullname}/releases/${id}`, options, cb); 761 | } 762 | 763 | /** 764 | * Get information about all releases 765 | * @see https://developer.github.com/v3/repos/releases/#list-releases-for-a-repository 766 | * @param {Requestable.callback} cb - will receive the release information 767 | * @return {Promise} - the promise for the http request 768 | */ 769 | listReleases(cb) { 770 | return this._request('GET', `/repos/${this.__fullname}/releases`, null, cb); 771 | } 772 | 773 | /** 774 | * Get information about a release 775 | * @see https://developer.github.com/v3/repos/releases/#get-a-single-release 776 | * @param {string} id - the id of the release 777 | * @param {Requestable.callback} cb - will receive the release information 778 | * @return {Promise} - the promise for the http request 779 | */ 780 | getRelease(id, cb) { 781 | return this._request('GET', `/repos/${this.__fullname}/releases/${id}`, null, cb); 782 | } 783 | 784 | /** 785 | * Delete a release 786 | * @see https://developer.github.com/v3/repos/releases/#delete-a-release 787 | * @param {string} id - the release to be deleted 788 | * @param {Requestable.callback} cb - will receive true if the operation is successful 789 | * @return {Promise} - the promise for the http request 790 | */ 791 | deleteRelease(id, cb) { 792 | return this._request('DELETE', `/repos/${this.__fullname}/releases/${id}`, null, cb); 793 | } 794 | 795 | /** 796 | * Merge a pull request 797 | * @see https://developer.github.com/v3/pulls/#merge-a-pull-request-merge-button 798 | * @param {number|string} number - the number of the pull request to merge 799 | * @param {Object} options - the merge options for the pull request 800 | * @param {Requestable.callback} [cb] - will receive the merge information if the operation is successful 801 | * @return {Promise} - the promise for the http request 802 | */ 803 | mergePullRequest(number, options, cb) { 804 | return this._request('PUT', `/repos/${this.__fullname}/pulls/${number}/merge`, options, cb); 805 | } 806 | } 807 | 808 | module.exports = Repository; 809 | -------------------------------------------------------------------------------- /lib/Requestable.js: -------------------------------------------------------------------------------- 1 | /** 2 | * @file 3 | * @copyright 2016 Yahoo Inc. 4 | * @license Licensed under {@link https://spdx.org/licenses/BSD-3-Clause-Clear.html BSD-3-Clause-Clear}. 5 | * Github.js is freely distributable. 6 | */ 7 | 8 | import axios from 'axios'; 9 | import debug from 'debug'; 10 | import {Base64} from 'js-base64'; 11 | import {polyfill} from 'es6-promise'; 12 | 13 | const log = debug('github:request'); 14 | 15 | if (typeof Promise === 'undefined') { 16 | polyfill(); 17 | } 18 | 19 | /** 20 | * The error structure returned when a network call fails 21 | */ 22 | class ResponseError extends Error { 23 | /** 24 | * Construct a new ResponseError 25 | * @param {string} message - an message to return instead of the the default error message 26 | * @param {string} path - the requested path 27 | * @param {Object} response - the object returned by Axios 28 | */ 29 | constructor(message, path, response) { 30 | super(message); 31 | this.path = path; 32 | this.request = response.config; 33 | this.response = response; 34 | this.status = response.status; 35 | } 36 | } 37 | 38 | /** 39 | * Requestable wraps the logic for making http requests to the API 40 | */ 41 | class Requestable { 42 | /** 43 | * Either a username and password or an oauth token for Github 44 | * @typedef {Object} Requestable.auth 45 | * @prop {string} [username] - the Github username 46 | * @prop {string} [password] - the user's password 47 | * @prop {token} [token] - an OAuth token 48 | */ 49 | /** 50 | * Initialize the http internals. 51 | * @param {Requestable.auth} [auth] - the credentials to authenticate to Github. If auth is 52 | * not provided request will be made unauthenticated 53 | * @param {string} [apiBase=https://api.github.com] - the base Github API URL 54 | */ 55 | constructor(auth, apiBase) { 56 | this.__apiBase = apiBase || 'https://api.github.com'; 57 | this.__auth = { 58 | token: auth.token, 59 | username: auth.username, 60 | password: auth.password 61 | }; 62 | 63 | if (auth.token) { 64 | this.__authorizationHeader = 'token ' + auth.token; 65 | } else if (auth.username && auth.password) { 66 | this.__authorizationHeader = 'Basic ' + Base64.encode(auth.username + ':' + auth.password); 67 | } 68 | } 69 | 70 | /** 71 | * Compute the URL to use to make a request. 72 | * @private 73 | * @param {string} path - either a URL relative to the API base or an absolute URL 74 | * @return {string} - the URL to use 75 | */ 76 | __getURL(path) { 77 | let url = path; 78 | 79 | if (path.indexOf('//') === -1) { 80 | url = this.__apiBase + path; 81 | } 82 | 83 | let newCacheBuster = 'timestamp=' + new Date().getTime(); 84 | return url.replace(/(timestamp=\d+)/, newCacheBuster); 85 | } 86 | 87 | /** 88 | * Compute the headers required for an API request. 89 | * @private 90 | * @param {boolean} raw - if the request should be treated as JSON or as a raw request 91 | * @return {Object} - the headers to use in the request 92 | */ 93 | __getRequestHeaders(raw) { 94 | let headers = { 95 | 'Accept': raw ? 'application/vnd.github.v3.raw+json' : 'application/vnd.github.v3+json', 96 | 'Content-Type': 'application/json;charset=UTF-8' 97 | }; 98 | 99 | if (this.__authorizationHeader) { 100 | headers.Authorization = this.__authorizationHeader; 101 | } 102 | 103 | return headers; 104 | } 105 | 106 | /** 107 | * Sets the default options for API requests 108 | * @protected 109 | * @param {Object} [requestOptions={}] - the current options for the request 110 | * @return {Object} - the options to pass to the request 111 | */ 112 | _getOptionsWithDefaults(requestOptions = {}) { 113 | if (!(requestOptions.visibility || requestOptions.affiliation)) { 114 | requestOptions.type = requestOptions.type || 'all'; 115 | } 116 | requestOptions.sort = requestOptions.sort || 'updated'; 117 | requestOptions.per_page = requestOptions.per_page || '100'; // eslint-disable-line 118 | 119 | return requestOptions; 120 | } 121 | 122 | /** 123 | * if a `Date` is passed to this function it will be converted to an ISO string 124 | * @param {*} date - the object to attempt to cooerce into an ISO date string 125 | * @return {string} - the ISO representation of `date` or whatever was passed in if it was not a date 126 | */ 127 | _dateToISO(date) { 128 | if (date && (date instanceof Date)) { 129 | date = date.toISOString(); 130 | } 131 | 132 | return date; 133 | } 134 | 135 | /** 136 | * A function that receives the result of the API request. 137 | * @callback Requestable.callback 138 | * @param {Requestable.Error} error - the error returned by the API or `null` 139 | * @param {(Object|true)} result - the data returned by the API or `true` if the API returns `204 No Content` 140 | * @param {Object} request - the raw {@linkcode https://github.com/mzabriskie/axios#response-schema Response} 141 | */ 142 | /** 143 | * Make a request. 144 | * @param {string} method - the method for the request (GET, PUT, POST, DELETE) 145 | * @param {string} path - the path for the request 146 | * @param {*} [data] - the data to send to the server. For HTTP methods that don't have a body the data 147 | * will be sent as query parameters 148 | * @param {Requestable.callback} [cb] - the callback for the request 149 | * @param {boolean} [raw=false] - if the request should be sent as raw. If this is a falsy value then the 150 | * request will be made as JSON 151 | * @return {Promise} - the Promise for the http request 152 | */ 153 | _request(method, path, data, cb, raw) { 154 | const url = this.__getURL(path); 155 | const headers = this.__getRequestHeaders(raw); 156 | let queryParams = {}; 157 | 158 | const shouldUseDataAsParams = data && (typeof data === 'object') && methodHasNoBody(method); 159 | if (shouldUseDataAsParams) { 160 | queryParams = data; 161 | data = undefined; 162 | } 163 | 164 | const config = { 165 | url: url, 166 | method: method, 167 | headers: headers, 168 | params: queryParams, 169 | data: data, 170 | responseType: raw ? 'text' : 'json' 171 | }; 172 | 173 | log(`${config.method} to ${config.url}`); 174 | const requestPromise = axios(config).catch(callbackErrorOrThrow(cb, path)); 175 | 176 | if (cb) { 177 | requestPromise.then((response) => { 178 | cb(null, response.data || true, response); 179 | }); 180 | } 181 | 182 | return requestPromise; 183 | } 184 | 185 | /** 186 | * Make a request to an endpoint the returns 204 when true and 404 when false 187 | * @param {string} path - the path to request 188 | * @param {Object} data - any query parameters for the request 189 | * @param {Requestable.callback} cb - the callback that will receive `true` or `false` 190 | * @param {method} [method=GET] - HTTP Method to use 191 | * @return {Promise} - the promise for the http request 192 | */ 193 | _request204or404(path, data, cb, method = 'GET') { 194 | return this._request(method, path, data) 195 | .then(function success(response) { 196 | if (cb) { 197 | cb(null, true, response); 198 | } 199 | return true; 200 | }, function failure(response) { 201 | if (response.status === 404) { 202 | if (cb) { 203 | cb(null, false, response); 204 | } 205 | return false; 206 | } 207 | 208 | if (cb) { 209 | cb(response); 210 | } 211 | throw response; 212 | }); 213 | } 214 | 215 | /** 216 | * Make a request and fetch all the available data. Github will paginate responses so for queries 217 | * that might span multiple pages this method is preferred to {@link Requestable#request} 218 | * @param {string} path - the path to request 219 | * @param {Object} options - the query parameters to include 220 | * @param {Requestable.callback} [cb] - the function to receive the data. The returned data will always be an array. 221 | * @param {Object[]} results - the partial results. This argument is intended for interal use only. 222 | * @return {Promise} - a promise which will resolve when all pages have been fetched 223 | * @deprecated This will be folded into {@link Requestable#_request} in the 2.0 release. 224 | */ 225 | _requestAllPages(path, options, cb, results) { 226 | results = results || []; 227 | 228 | return this._request('GET', path, options) 229 | .then((response) => { 230 | let thisGroup; 231 | if (response.data instanceof Array) { 232 | thisGroup = response.data; 233 | } else if (response.data.items instanceof Array) { 234 | thisGroup = response.data.items; 235 | } else { 236 | let message = `cannot figure out how to append ${response.data} to the result set`; 237 | throw new ResponseError(message, path, response); 238 | } 239 | results.push.apply(results, thisGroup); 240 | 241 | const nextUrl = getNextPage(response.headers.link); 242 | if (nextUrl) { 243 | log(`getting next page: ${nextUrl}`); 244 | return this._requestAllPages(nextUrl, options, cb, results); 245 | } 246 | 247 | if (cb) { 248 | cb(null, results, response); 249 | } 250 | 251 | response.data = results; 252 | return response; 253 | }).catch(callbackErrorOrThrow(cb, path)); 254 | } 255 | } 256 | 257 | module.exports = Requestable; 258 | 259 | // ////////////////////////// // 260 | // Private helper functions // 261 | // ////////////////////////// // 262 | const METHODS_WITH_NO_BODY = ['GET', 'HEAD', 'DELETE']; 263 | function methodHasNoBody(method) { 264 | return METHODS_WITH_NO_BODY.indexOf(method) !== -1; 265 | } 266 | 267 | function getNextPage(linksHeader = '') { 268 | const links = linksHeader.split(/\s*,\s*/); // splits and strips the urls 269 | return links.reduce(function(nextUrl, link) { 270 | if (link.search(/rel="next"/) !== -1) { 271 | return (link.match(/<(.*)>/) || [])[1]; 272 | } 273 | 274 | return nextUrl; 275 | }, undefined); 276 | } 277 | 278 | function callbackErrorOrThrow(cb, path) { 279 | return function handler(object) { 280 | let error; 281 | if (object.hasOwnProperty('config')) { 282 | const {status, statusText, config: {method, url}} = object; 283 | let message = (`${status} error making request ${method} ${url}: "${statusText}"`); 284 | error = new ResponseError(message, path, object); 285 | log(`${message} ${JSON.stringify(object.data)}`); 286 | } else { 287 | error = object; 288 | } 289 | if (cb) { 290 | log('going to error callback'); 291 | cb(error); 292 | } else { 293 | log('throwing error'); 294 | throw error; 295 | } 296 | }; 297 | } 298 | -------------------------------------------------------------------------------- /lib/Search.js: -------------------------------------------------------------------------------- 1 | /** 2 | * @file 3 | * @copyright 2013 Michael Aufreiter (Development Seed) and 2016 Yahoo Inc. 4 | * @license Licensed under {@link https://spdx.org/licenses/BSD-3-Clause-Clear.html BSD-3-Clause-Clear}. 5 | * Github.js is freely distributable. 6 | */ 7 | 8 | import Requestable from './Requestable'; 9 | import debug from 'debug'; 10 | const log = debug('github:search'); 11 | 12 | /** 13 | * Wrap the Search API 14 | */ 15 | class Search extends Requestable { 16 | /** 17 | * Create a Search 18 | * @param {Object} defaults - defaults for the search 19 | * @param {Requestable.auth} [auth] - information required to authenticate to Github 20 | * @param {string} [apiBase=https://api.github.com] - the base Github API URL 21 | */ 22 | constructor(defaults, auth, apiBase) { 23 | super(auth, apiBase); 24 | this.__defaults = this._getOptionsWithDefaults(defaults); 25 | } 26 | 27 | /** 28 | * Available search options 29 | * @see https://developer.github.com/v3/search/#parameters 30 | * @typedef {Object} Search.Params 31 | * @param {string} q - the query to make 32 | * @param {string} sort - the sort field, one of `stars`, `forks`, or `updated`. 33 | * Default is [best match](https://developer.github.com/v3/search/#ranking-search-results) 34 | * @param {string} order - the ordering, either `asc` or `desc` 35 | */ 36 | /** 37 | * Perform a search on the GitHub API 38 | * @private 39 | * @param {string} path - the scope of the search 40 | * @param {Search.Params} [withOptions] - additional parameters for the search 41 | * @param {Requestable.callback} [cb] - will receive the results of the search 42 | * @return {Promise} - the promise for the http request 43 | */ 44 | _search(path, withOptions = {}, cb = undefined) { 45 | let requestOptions = {}; 46 | Object.keys(this.__defaults).forEach((prop) => { 47 | requestOptions[prop] = this.__defaults[prop]; 48 | }); 49 | Object.keys(withOptions).forEach((prop) => { 50 | requestOptions[prop] = withOptions[prop]; 51 | }); 52 | 53 | log(`searching ${path} with options:`, requestOptions); 54 | return this._requestAllPages(`/search/${path}`, requestOptions, cb); 55 | } 56 | 57 | /** 58 | * Search for repositories 59 | * @see https://developer.github.com/v3/search/#search-repositories 60 | * @param {Search.Params} [options] - additional parameters for the search 61 | * @param {Requestable.callback} [cb] - will receive the results of the search 62 | * @return {Promise} - the promise for the http request 63 | */ 64 | forRepositories(options, cb) { 65 | return this._search('repositories', options, cb); 66 | } 67 | 68 | /** 69 | * Search for code 70 | * @see https://developer.github.com/v3/search/#search-code 71 | * @param {Search.Params} [options] - additional parameters for the search 72 | * @param {Requestable.callback} [cb] - will receive the results of the search 73 | * @return {Promise} - the promise for the http request 74 | */ 75 | forCode(options, cb) { 76 | return this._search('code', options, cb); 77 | } 78 | 79 | /** 80 | * Search for issues 81 | * @see https://developer.github.com/v3/search/#search-issues 82 | * @param {Search.Params} [options] - additional parameters for the search 83 | * @param {Requestable.callback} [cb] - will receive the results of the search 84 | * @return {Promise} - the promise for the http request 85 | */ 86 | forIssues(options, cb) { 87 | return this._search('issues', options, cb); 88 | } 89 | 90 | /** 91 | * Search for users 92 | * @see https://developer.github.com/v3/search/#search-users 93 | * @param {Search.Params} [options] - additional parameters for the search 94 | * @param {Requestable.callback} [cb] - will receive the results of the search 95 | * @return {Promise} - the promise for the http request 96 | */ 97 | forUsers(options, cb) { 98 | return this._search('users', options, cb); 99 | } 100 | } 101 | 102 | module.exports = Search; 103 | -------------------------------------------------------------------------------- /lib/Team.js: -------------------------------------------------------------------------------- 1 | /** 2 | * @file 3 | * @copyright 2016 Matt Smith (Development Seed) 4 | * @license Licensed under {@link https://spdx.org/licenses/BSD-3-Clause-Clear.html BSD-3-Clause-Clear}. 5 | * Github.js is freely distributable. 6 | */ 7 | 8 | import Requestable from './Requestable'; 9 | import debug from 'debug'; 10 | const log = debug('github:team'); 11 | 12 | /** 13 | * A Team allows scoping of API requests to a particular Github Organization Team. 14 | */ 15 | class Team extends Requestable { 16 | /** 17 | * Create a Team. 18 | * @param {string} [teamId] - the id for the team 19 | * @param {Requestable.auth} [auth] - information required to authenticate to Github 20 | * @param {string} [apiBase=https://api.github.com] - the base Github API URL 21 | */ 22 | constructor(teamId, auth, apiBase) { 23 | super(auth, apiBase); 24 | this.__teamId = teamId; 25 | } 26 | 27 | /** 28 | * Get Team information 29 | * @see https://developer.github.com/v3/orgs/teams/#get-team 30 | * @param {Requestable.callback} [cb] - will receive the team 31 | * @return {Promise} - the promise for the http request 32 | */ 33 | getTeam(cb) { 34 | log(`Fetching Team ${this.__teamId}`); 35 | return this._request('Get', `/teams/${this.__teamId}`, undefined, cb); 36 | } 37 | 38 | /** 39 | * List the Team's repositories 40 | * @see https://developer.github.com/v3/orgs/teams/#list-team-repos 41 | * @param {Requestable.callback} [cb] - will receive the list of repositories 42 | * @return {Promise} - the promise for the http request 43 | */ 44 | listRepos(cb) { 45 | log(`Fetching repositories for Team ${this.__teamId}`); 46 | return this._requestAllPages(`/teams/${this.__teamId}/repos`, undefined, cb); 47 | } 48 | 49 | /** 50 | * Edit Team information 51 | * @see https://developer.github.com/v3/orgs/teams/#edit-team 52 | * @param {object} options - Parameters for team edit 53 | * @param {string} options.name - The name of the team 54 | * @param {string} [options.description] - Team description 55 | * @param {string} [options.repo_names] - Repos to add the team to 56 | * @param {string} [options.privacy=secret] - The level of privacy the team should have. Can be either one 57 | * of: `secret`, or `closed` 58 | * @param {Requestable.callback} [cb] - will receive the updated team 59 | * @return {Promise} - the promise for the http request 60 | */ 61 | editTeam(options, cb) { 62 | log(`Editing Team ${this.__teamId}`); 63 | return this._request('PATCH', `/teams/${this.__teamId}`, options, cb); 64 | } 65 | 66 | /** 67 | * List the users who are members of the Team 68 | * @see https://developer.github.com/v3/orgs/teams/#list-team-members 69 | * @param {object} options - Parameters for listing team users 70 | * @param {string} [options.role=all] - can be one of: `all`, `maintainer`, or `member` 71 | * @param {Requestable.callback} [cb] - will receive the list of users 72 | * @return {Promise} - the promise for the http request 73 | */ 74 | listMembers(options, cb) { 75 | log(`Getting members of Team ${this.__teamId}`); 76 | return this._requestAllPages(`/teams/${this.__teamId}/members`, options, cb); 77 | } 78 | 79 | /** 80 | * Get Team membership status for a user 81 | * @see https://developer.github.com/v3/orgs/teams/#get-team-membership 82 | * @param {string} username - can be one of: `all`, `maintainer`, or `member` 83 | * @param {Requestable.callback} [cb] - will receive the membership status of a user 84 | * @return {Promise} - the promise for the http request 85 | */ 86 | getMembership(username, cb) { 87 | log(`Getting membership of user ${username} in Team ${this.__teamId}`); 88 | return this._request('GET', `/teams/${this.__teamId}/memberships/${username}`, undefined, cb); 89 | } 90 | 91 | /** 92 | * Add a member to the Team 93 | * @see https://developer.github.com/v3/orgs/teams/#add-team-membership 94 | * @param {string} username - can be one of: `all`, `maintainer`, or `member` 95 | * @param {object} options - Parameters for adding a team member 96 | * @param {string} [options.role=member] - The role that this user should have in the team. Can be one 97 | * of: `member`, or `maintainer` 98 | * @param {Requestable.callback} [cb] - will receive the membership status of added user 99 | * @return {Promise} - the promise for the http request 100 | */ 101 | addMembership(username, options, cb) { 102 | log(`Adding user ${username} to Team ${this.__teamId}`); 103 | return this._request('PUT', `/teams/${this.__teamId}/memberships/${username}`, options, cb); 104 | } 105 | 106 | /** 107 | * Get repo management status for team 108 | * @see https://developer.github.com/v3/orgs/teams/#remove-team-membership 109 | * @param {string} owner - Organization name 110 | * @param {string} repo - Repo name 111 | * @param {Requestable.callback} [cb] - will receive the membership status of added user 112 | * @return {Promise} - the promise for the http request 113 | */ 114 | isManagedRepo(owner, repo, cb) { 115 | log(`Getting repo management by Team ${this.__teamId} for repo ${owner}/${repo}`); 116 | return this._request204or404(`/teams/${this.__teamId}/repos/${owner}/${repo}`, undefined, cb); 117 | } 118 | 119 | /** 120 | * Add or Update repo management status for team 121 | * @see https://developer.github.com/v3/orgs/teams/#add-or-update-team-repository 122 | * @param {string} owner - Organization name 123 | * @param {string} repo - Repo name 124 | * @param {object} options - Parameters for adding or updating repo management for the team 125 | * @param {string} [options.permission] - The permission to grant the team on this repository. Can be one 126 | * of: `pull`, `push`, or `admin` 127 | * @param {Requestable.callback} [cb] - will receive the membership status of added user 128 | * @return {Promise} - the promise for the http request 129 | */ 130 | manageRepo(owner, repo, options, cb) { 131 | log(`Adding or Updating repo management by Team ${this.__teamId} for repo ${owner}/${repo}`); 132 | return this._request204or404(`/teams/${this.__teamId}/repos/${owner}/${repo}`, options, cb, 'PUT'); 133 | } 134 | 135 | /** 136 | * Remove repo management status for team 137 | * @see https://developer.github.com/v3/orgs/teams/#remove-team-repository 138 | * @param {string} owner - Organization name 139 | * @param {string} repo - Repo name 140 | * @param {Requestable.callback} [cb] - will receive the membership status of added user 141 | * @return {Promise} - the promise for the http request 142 | */ 143 | unmanageRepo(owner, repo, cb) { 144 | log(`Remove repo management by Team ${this.__teamId} for repo ${owner}/${repo}`); 145 | return this._request204or404(`/teams/${this.__teamId}/repos/${owner}/${repo}`, undefined, cb, 'DELETE'); 146 | } 147 | 148 | /** 149 | * Delete Team 150 | * @see https://developer.github.com/v3/orgs/teams/#delete-team 151 | * @param {Requestable.callback} [cb] - will receive the list of repositories 152 | * @return {Promise} - the promise for the http request 153 | */ 154 | deleteTeam(cb) { 155 | log(`Deleting Team ${this.__teamId}`); 156 | return this._request204or404(`/teams/${this.__teamId}`, undefined, cb, 'DELETE'); 157 | } 158 | } 159 | 160 | module.exports = Team; 161 | -------------------------------------------------------------------------------- /lib/User.js: -------------------------------------------------------------------------------- 1 | /** 2 | * @file 3 | * @copyright 2013 Michael Aufreiter (Development Seed) and 2016 Yahoo Inc. 4 | * @license Licensed under {@link https://spdx.org/licenses/BSD-3-Clause-Clear.html BSD-3-Clause-Clear}. 5 | * Github.js is freely distributable. 6 | */ 7 | 8 | import Requestable from './Requestable'; 9 | import debug from 'debug'; 10 | const log = debug('github:user'); 11 | 12 | /** 13 | * A User allows scoping of API requests to a particular Github user. 14 | */ 15 | class User extends Requestable { 16 | /** 17 | * Create a User. 18 | * @param {string} [username] - the user to use for user-scoped queries 19 | * @param {Requestable.auth} [auth] - information required to authenticate to Github 20 | * @param {string} [apiBase=https://api.github.com] - the base Github API URL 21 | */ 22 | constructor(username, auth, apiBase) { 23 | super(auth, apiBase); 24 | this.__user = username; 25 | } 26 | 27 | /** 28 | * Get the url for the request. (dependent on if we're requesting for the authenticated user or not) 29 | * @private 30 | * @param {string} endpoint - the endpoint being requested 31 | * @return {string} - the resolved endpoint 32 | */ 33 | __getScopedUrl(endpoint) { 34 | if (this.__user) { 35 | return endpoint ? 36 | `/users/${this.__user}/${endpoint}` : 37 | `/users/${this.__user}` 38 | ; 39 | 40 | } else { // eslint-disable-line 41 | switch (endpoint) { 42 | case '': 43 | return '/user'; 44 | 45 | case 'notifications': 46 | case 'gists': 47 | return `/${endpoint}`; 48 | 49 | default: 50 | return `/user/${endpoint}`; 51 | } 52 | } 53 | } 54 | 55 | /** 56 | * List the user's repositories 57 | * @see https://developer.github.com/v3/repos/#list-user-repositories 58 | * @param {Object} [options={}] - any options to refine the search 59 | * @param {Requestable.callback} [cb] - will receive the list of repositories 60 | * @return {Promise} - the promise for the http request 61 | */ 62 | listRepos(options, cb) { 63 | if (typeof options === 'function') { 64 | cb = options; 65 | options = {}; 66 | } 67 | 68 | options = this._getOptionsWithDefaults(options); 69 | 70 | log(`Fetching repositories with options: ${JSON.stringify(options)}`); 71 | return this._requestAllPages(this.__getScopedUrl('repos'), options, cb); 72 | } 73 | 74 | /** 75 | * List the orgs that the user belongs to 76 | * @see https://developer.github.com/v3/orgs/#list-user-organizations 77 | * @param {Requestable.callback} [cb] - will receive the list of organizations 78 | * @return {Promise} - the promise for the http request 79 | */ 80 | listOrgs(cb) { 81 | return this._request('GET', this.__getScopedUrl('orgs'), null, cb); 82 | } 83 | 84 | /** 85 | * List the user's gists 86 | * @see https://developer.github.com/v3/gists/#list-a-users-gists 87 | * @param {Requestable.callback} [cb] - will receive the list of gists 88 | * @return {Promise} - the promise for the http request 89 | */ 90 | listGists(cb) { 91 | return this._request('GET', this.__getScopedUrl('gists'), null, cb); 92 | } 93 | 94 | /** 95 | * List the user's notifications 96 | * @see https://developer.github.com/v3/activity/notifications/#list-your-notifications 97 | * @param {Object} [options={}] - any options to refine the search 98 | * @param {Requestable.callback} [cb] - will receive the list of repositories 99 | * @return {Promise} - the promise for the http request 100 | */ 101 | listNotifications(options, cb) { 102 | options = options || {}; 103 | if (typeof options === 'function') { 104 | cb = options; 105 | options = {}; 106 | } 107 | 108 | options.since = this._dateToISO(options.since); 109 | options.before = this._dateToISO(options.before); 110 | 111 | return this._request('GET', this.__getScopedUrl('notifications'), options, cb); 112 | } 113 | 114 | /** 115 | * Show the user's profile 116 | * @see https://developer.github.com/v3/users/#get-a-single-user 117 | * @param {Requestable.callback} [cb] - will receive the user's information 118 | * @return {Promise} - the promise for the http request 119 | */ 120 | getProfile(cb) { 121 | return this._request('GET', this.__getScopedUrl(''), null, cb); 122 | } 123 | 124 | /** 125 | * Gets the list of starred repositories for the user 126 | * @see https://developer.github.com/v3/activity/starring/#list-repositories-being-starred 127 | * @param {Requestable.callback} [cb] - will receive the list of starred repositories 128 | * @return {Promise} - the promise for the http request 129 | */ 130 | listStarredRepos(cb) { 131 | let requestOptions = this._getOptionsWithDefaults(); 132 | return this._requestAllPages(this.__getScopedUrl('starred'), requestOptions, cb); 133 | } 134 | 135 | /** 136 | * Have the authenticated user follow this user 137 | * @see https://developer.github.com/v3/users/followers/#follow-a-user 138 | * @param {string} username - the user to follow 139 | * @param {Requestable.callback} [cb] - will receive true if the request succeeds 140 | * @return {Promise} - the promise for the http request 141 | */ 142 | follow(username, cb) { 143 | return this._request('PUT', `/user/following/${this.__user}`, null, cb); 144 | } 145 | 146 | /** 147 | * Have the currently authenticated user unfollow this user 148 | * @see https://developer.github.com/v3/users/followers/#follow-a-user 149 | * @param {string} username - the user to unfollow 150 | * @param {Requestable.callback} [cb] - receives true if the request succeeds 151 | * @return {Promise} - the promise for the http request 152 | */ 153 | unfollow(username, cb) { 154 | return this._request('DELETE', `/user/following/${this.__user}`, null, cb); 155 | } 156 | 157 | /** 158 | * Create a new repository for the currently authenticated user 159 | * @see https://developer.github.com/v3/repos/#create 160 | * @param {object} options - the repository definition 161 | * @param {Requestable.callback} [cb] - will receive the API response 162 | * @return {Promise} - the promise for the http request 163 | */ 164 | createRepo(options, cb) { 165 | return this._request('POST', '/user/repos', options, cb); 166 | } 167 | } 168 | 169 | module.exports = User; 170 | -------------------------------------------------------------------------------- /mocha.opts: -------------------------------------------------------------------------------- 1 | --compilers js:babel-register 2 | --timeout 15000 3 | --slow 5000 4 | -------------------------------------------------------------------------------- /package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "github-api", 3 | "version": "2.4.0", 4 | "license": "BSD-3-Clause-Clear", 5 | "description": "A higher-level wrapper around the Github API.", 6 | "main": "dist/components/GitHub.js", 7 | "contributors": [ 8 | "Ændrew Rininsland (http://www.aendrew.com)", 9 | "Aurelio De Rosa (http://www.audero.it/)", 10 | "Clay Reimann (http://clayreimann.me)", 11 | "Michael Aufreiter (http://substance.io)" 12 | ], 13 | "readmeFilename": "README.md", 14 | "scripts": { 15 | "clean": "gulp clean", 16 | "build": "gulp build", 17 | "test": "mocha --opts ./mocha.opts test/*.spec.js", 18 | "test-verbose": "DEBUG=github* npm test", 19 | "lint": "gulp lint", 20 | "make-docs": "node_modules/.bin/jsdoc -c .jsdoc.json --verbose", 21 | "release": "./release.sh" 22 | }, 23 | "babel": { 24 | "presets": [ 25 | "es2015" 26 | ], 27 | "plugins": [ 28 | [ 29 | "transform-es2015-modules-umd", 30 | { 31 | "globals": { 32 | "es6-promise": "Promise" 33 | } 34 | } 35 | ] 36 | ], 37 | "env": { 38 | "development": { 39 | "sourceMaps": "inline" 40 | } 41 | } 42 | }, 43 | "files": [ 44 | "dist/*", 45 | "lib/*" 46 | ], 47 | "dependencies": { 48 | "axios": "^0.10.0", 49 | "debug": "^2.2.0", 50 | "es6-promise": "^3.0.2", 51 | "js-base64": "^2.1.9", 52 | "utf8": "^2.1.1" 53 | }, 54 | "devDependencies": { 55 | "babel-core": "^6.7.7", 56 | "babel-plugin-transform-es2015-modules-umd": "^6.5.0", 57 | "babel-preset-es2015": "^6.5.0", 58 | "babel-register": "^6.7.2", 59 | "babelify": "^7.3.0", 60 | "browserify": "^13.0.0", 61 | "codecov": "^1.0.1", 62 | "del": "^2.2.0", 63 | "eslint-config-google": "^0.5.0", 64 | "eslint-plugin-mocha": "^2.2.0", 65 | "gulp": "^3.9.0", 66 | "gulp-babel": "^6.1.2", 67 | "gulp-eslint": "^2.0.0", 68 | "gulp-jscs": "^3.0.2", 69 | "gulp-jscs-stylish": "^1.3.0", 70 | "gulp-rename": "^1.2.2", 71 | "gulp-sourcemaps": "^1.6.0", 72 | "gulp-uglify": "^1.5.1", 73 | "jsdoc": "^3.4.0", 74 | "minami": "^1.1.1", 75 | "mocha": "^2.3.4", 76 | "must": "^0.13.1", 77 | "nock": "^8.0.0", 78 | "vinyl-buffer": "^1.0.0", 79 | "vinyl-source-stream": "^1.1.0" 80 | }, 81 | "repository": { 82 | "type": "git", 83 | "url": "git://github.com/michael/github.git" 84 | }, 85 | "keywords": [ 86 | "github", 87 | "api" 88 | ], 89 | "bugs": { 90 | "url": "https://github.com/michael/github/issues" 91 | } 92 | } 93 | -------------------------------------------------------------------------------- /release.sh: -------------------------------------------------------------------------------- 1 | #!/bin/bash 2 | # This is the automated release script 3 | 4 | # make sure all our dependencies are installed so we can publish docs 5 | npm install 6 | 7 | # guard against stupid 8 | if [ -z "$1" ]; then 9 | echo "You must specify a new version level: [patch, minor, major]"; 10 | exit 1; 11 | fi 12 | 13 | # bump the version 14 | echo "npm version $1" 15 | npm version $1 16 | git push 17 | git push --tags 18 | 19 | # start from a clean state 20 | rm -rf docs/ out/ 21 | mkdir out 22 | 23 | # build the docs 24 | npm run make-docs 25 | VERSION=`ls docs/github-api` 26 | 27 | # switch to gh-pages and add the docs 28 | mv docs/github-api/* out/ 29 | rm -rf docs/ 30 | 31 | git checkout gh-pages 32 | mv out/* docs/ 33 | echo $VERSION >> _data/versions.csv 34 | git add . 35 | git co -m "adding docs for v$VERSION" 36 | git push 37 | -------------------------------------------------------------------------------- /test/.eslintrc.yaml: -------------------------------------------------------------------------------- 1 | --- 2 | extends: ../.eslintrc.yaml 3 | plugins: 4 | - mocha 5 | env: 6 | mocha: true 7 | rules: 8 | handle-callback-err: off 9 | mocha/no-exclusive-tests: 2 10 | -------------------------------------------------------------------------------- /test/auth.spec.js: -------------------------------------------------------------------------------- 1 | import expect from 'must'; 2 | 3 | import Github from '../lib/GitHub'; 4 | import testUser from './fixtures/user.json'; 5 | import {assertSuccessful, assertFailure} from './helpers/callbacks'; 6 | 7 | describe('Github', function() { 8 | let github; 9 | let user; 10 | 11 | describe('with authentication', function() { 12 | before(function() { 13 | github = new Github({ 14 | username: testUser.USERNAME, 15 | password: testUser.PASSWORD, 16 | auth: 'basic' 17 | }); 18 | 19 | user = github.getUser(); 20 | }); 21 | 22 | // 200ms between tests so that Github has a chance to settle 23 | beforeEach(function(done) { 24 | setTimeout(done, 200); 25 | }); 26 | 27 | it('should authenticate and return no errors', function(done) { 28 | user.listNotifications(assertSuccessful(done)); 29 | }); 30 | }); 31 | 32 | describe('without authentication', function() { 33 | before(function() { 34 | github = new Github(); 35 | }); 36 | 37 | // 200ms between tests so that Github has a chance to settle 38 | beforeEach(function(done) { 39 | setTimeout(done, 200); 40 | }); 41 | 42 | it('should read public information', function(done) { 43 | let gist = github.getGist('f1c0f84e53aa6b98ec03'); 44 | 45 | gist.read(function(err, res, xhr) { 46 | try { 47 | expect(err).not.to.exist(); 48 | expect(res).to.exist(); 49 | expect(xhr).to.be.an.object(); 50 | 51 | done(); 52 | } catch (e) { 53 | try { 54 | if (err && err.response.headers['x-ratelimit-remaining'] === '0') { 55 | done(); 56 | return; 57 | } 58 | } catch (e2) { 59 | done(e); 60 | } 61 | 62 | done(e); 63 | } 64 | }); 65 | }); 66 | }); 67 | 68 | describe('with bad authentication', function() { 69 | before(function() { 70 | github = new Github({ 71 | username: testUser.USERNAME, 72 | password: 'fake124', 73 | auth: 'basic' 74 | }); 75 | 76 | user = github.getUser(); 77 | }); 78 | 79 | // 200ms between tests so that Github has a chance to settle 80 | beforeEach(function(done) { 81 | setTimeout(done, 200); 82 | }); 83 | 84 | it('should fail authentication and return err', function(done) { 85 | user.listNotifications(assertFailure(done, function(err) { 86 | expect(err.status).to.be.equal(401, 'Return 401 status for bad auth'); 87 | expect(err.response.data.message).to.equal('Bad credentials'); 88 | 89 | done(); 90 | })); 91 | }); 92 | }); 93 | }); 94 | -------------------------------------------------------------------------------- /test/dist.spec/index.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | Mocha Tests 5 | 6 | 7 | 8 |
9 | 10 | 11 | 12 | 13 | 26 | 30 | 31 | 32 | -------------------------------------------------------------------------------- /test/error.spec.js: -------------------------------------------------------------------------------- 1 | import assert from 'assert'; 2 | import expect from 'must'; 3 | import nock from 'nock'; 4 | 5 | import Github from '../lib/GitHub'; 6 | import testUser from './fixtures/user.json'; 7 | import {assertSuccessful, assertFailure} from './helpers/callbacks'; 8 | import fixtureExhausted from './fixtures/repos-ratelimit-exhausted.js'; 9 | import fixtureOk from './fixtures/repos-ratelimit-ok.js'; 10 | 11 | describe('Rate limit error', function() { 12 | let github; 13 | let user; 14 | let scope; 15 | 16 | before(function() { 17 | github = new Github(); 18 | user = github.getUser(testUser.USERNAME); 19 | }); 20 | 21 | beforeEach(function() { 22 | scope = fixtureExhausted(); 23 | }); 24 | 25 | it('should reject promise with 403 error', function() { 26 | return user.listRepos().then(function() { 27 | assert.fail(undefined, undefined, 'Promise was resolved instead of rejected'); 28 | }, function(error) { 29 | expect(error).to.be.an.error(); 30 | expect(error).to.have.own('response'); 31 | expect(error.response).to.have.own('status'); 32 | expect(error.response.status).to.be(403); 33 | }); 34 | }); 35 | 36 | it('should call callback', function(done) { 37 | user.listRepos(assertFailure(done, function(error) { 38 | expect(error).to.be.an.error(); 39 | expect(error).to.have.own('response'); 40 | expect(error.response).to.have.own('status'); 41 | expect(error.response.status).to.be(403); 42 | done(); 43 | })); 44 | }); 45 | 46 | afterEach(function() { 47 | scope.done(); 48 | nock.cleanAll(); 49 | }); 50 | }); 51 | 52 | describe('Rate limit OK', function() { 53 | let github; 54 | let user; 55 | let scope; 56 | 57 | before(function() { 58 | github = new Github(); 59 | user = github.getUser(testUser.USERNAME); 60 | }); 61 | 62 | beforeEach(function() { 63 | scope = fixtureOk(); 64 | }); 65 | 66 | it('should resolve promise', function() { 67 | return expect(user.listRepos()).to.resolve.to.object(); 68 | }); 69 | 70 | it('should call callback with array of results', function(done) { 71 | user.listRepos(assertSuccessful(done, function(error, result) { 72 | expect(error).is.not.an.error(); 73 | expect(error).is.not.truthy(); 74 | expect(result).is.array(); 75 | done(); 76 | })); 77 | }); 78 | 79 | afterEach(function() { 80 | scope.done(); 81 | nock.cleanAll(); 82 | }); 83 | }); 84 | -------------------------------------------------------------------------------- /test/fixtures/gh.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/github/github-1/78a98b167ff7ddd343110e8b96c723110d2f6350/test/fixtures/gh.png -------------------------------------------------------------------------------- /test/fixtures/gist.json: -------------------------------------------------------------------------------- 1 | { 2 | "description": "This is a test gist", 3 | "public": true, 4 | "files": { 5 | "README.md": { 6 | "content": "Hello World" 7 | } 8 | } 9 | } 10 | -------------------------------------------------------------------------------- /test/fixtures/imageBlob.js: -------------------------------------------------------------------------------- 1 | 'use strict'; 2 | 3 | function loadImage(imageReady) { 4 | if (typeof window === 'undefined') { // We're in NodeJS 5 | loadInNode(imageReady); 6 | } else { // We're in the browser 7 | if (typeof window._phantom !== 'undefined') { 8 | loadInPhantom(imageReady); 9 | } else { 10 | loadInRealBrowser(imageReady); 11 | } 12 | } 13 | } 14 | 15 | function loadInNode(imageReady) { 16 | var fs = require('fs'); 17 | var path = require('path'); 18 | 19 | var imageBlob = fs.readFileSync(path.join(__dirname, 'gh.png')); // This is a Buffer(). 20 | var imageB64 = imageBlob.toString('base64'); 21 | 22 | imageReady(imageB64, imageBlob); 23 | } 24 | 25 | function loadInPhantom(imageReady) { 26 | var xhr = new XMLHttpRequest(); 27 | 28 | xhr.responseType = 'blob'; 29 | xhr.open('GET', 'base/test/fixtures/gh.png'); 30 | xhr.onload = function() { 31 | var reader = new FileReader(); 32 | 33 | reader.onloadend = function() { 34 | var imageB64 = btoa(reader.result); 35 | var imageBlob = reader.result; 36 | 37 | imageReady(imageB64, imageBlob); 38 | }; 39 | 40 | reader.readAsBinaryString(xhr.response); 41 | }; 42 | 43 | xhr.send(); 44 | } 45 | 46 | function loadInRealBrowser(imageReady) { 47 | // jscs:disable 48 | var imageB64 = 'iVBORw0KGgoAAAANSUhEUgAAACsAAAAmCAAAAAB4qD3CAAABgElEQVQ4y9XUsUocURQGYN/pAyMWBhGtrEIMiFiooGuVIoYsSBAsRSQvYGFWC4uFhUBYsilXLERQsDA20YAguIbo5PQp3F3inVFTheSvZoavGO79z+mJP0/Pv2nPtlfLpfLq9tljNquO62S8mj1kmy/8nrHm/Xaz1930bt5n1+SzVmyrilItsod9ON0td1V59xR9hwV2HsMRsbfROLo4amzsRcQw5vO2CZPJEU5CM2cXYTCxg7CY2mwIVhK7AkNZYg9g4CqxVwNwkNg6zOTKMQP1xFZgKWeXoJLYdSjl7BysJ7YBIzk7Ap8TewLOE3oOTtIz6y/64bfQn55ZTIAPd2gNTOTurcbzp7z50v1y/Pq2Q7Wczca8vFjG6LvbMo92hiPL96xO+eYVPkVExMdONetFXZ+l+eP9cuV7RER8a9PZwrloTXv2tfv285ZOt4rnrTXlydxCu9sZmGrdN8eXC3ATERHXsHD5wC7ZL3HdsaX9R3bUzlb7YWvn/9ipf93+An8cHsx3W3WHAAAAAElFTkSuQmCC'; 49 | var imageBlob = new Blob(); 50 | // jscs:enable 51 | 52 | imageReady(imageB64, imageBlob); 53 | } 54 | 55 | module.exports = loadImage; 56 | -------------------------------------------------------------------------------- /test/fixtures/record.js: -------------------------------------------------------------------------------- 1 | import fs from 'fs'; 2 | import nock from 'nock'; 3 | import path from 'path'; 4 | import GitHub from '../../lib/GitHub'; 5 | import testUser from './user.json'; 6 | 7 | const gh = new GitHub(); 8 | 9 | let fileName; 10 | gh.getRateLimit().getRateLimit() 11 | .then((resp) => { 12 | if (resp.data.rate.remaining === 0) { 13 | fileName = 'repos-ratelimit-exhausted.js'; 14 | } else { 15 | fileName = 'repos-ratelimit-ok.js'; 16 | } 17 | nock.recorder.rec({ 18 | dont_print: true 19 | }); 20 | gh.getUser(testUser.USERNAME).listRepos(); 21 | setTimeout(() => { 22 | const fixtures = nock.recorder.play(); 23 | const filePath = path.join(__dirname, fileName); 24 | const text = ('/* eslint-disable */\n' + 25 | 'import nock from \'nock\';\n' + 26 | 'export default function fixture() {\n' + 27 | ' let scope;\n' + 28 | ' scope = ' + fixtures.join('\nscope = ').trim().replace(/\n/g, '\n ') + '\n' + 29 | ' return scope;\n' + 30 | '}\n'); 31 | fs.writeFileSync(filePath, text); 32 | console.log('Wrote fixture data to', fileName); 33 | }, 10000); 34 | }).catch(console.error); 35 | -------------------------------------------------------------------------------- /test/fixtures/repos-ratelimit-exhausted.js: -------------------------------------------------------------------------------- 1 | /* eslint-disable */ 2 | import nock from 'nock'; 3 | export default function fixture() { 4 | let scope; 5 | scope = nock('https://api.github.com:443', {"encodedQueryParams":true}) 6 | .get('/users/mikedeboertest/repos') 7 | .query({"type":"all","sort":"updated","per_page":"100"}) 8 | .reply(403, {"message":"API rate limit exceeded for 174.20.8.171. (But here's the good news: Authenticated requests get a higher rate limit. Check out the documentation for more details.)","documentation_url":"https://developer.github.com/v3/#rate-limiting"}, { server: 'GitHub.com', 9 | date: 'Sat, 18 Jun 2016 11:50:00 GMT', 10 | 'content-type': 'application/json; charset=utf-8', 11 | 'content-length': '246', 12 | connection: 'close', 13 | status: '403 Forbidden', 14 | 'x-ratelimit-limit': '60', 15 | 'x-ratelimit-remaining': '0', 16 | 'x-ratelimit-reset': '1466253529', 17 | 'x-github-media-type': 'github.v3; format=json', 18 | 'access-control-expose-headers': 'ETag, Link, X-GitHub-OTP, X-RateLimit-Limit, X-RateLimit-Remaining, X-RateLimit-Reset, X-OAuth-Scopes, X-Accepted-OAuth-Scopes, X-Poll-Interval', 19 | 'access-control-allow-origin': '*', 20 | 'content-security-policy': 'default-src \'none\'', 21 | 'strict-transport-security': 'max-age=31536000; includeSubdomains; preload', 22 | 'x-content-type-options': 'nosniff', 23 | 'x-frame-options': 'deny', 24 | 'x-xss-protection': '1; mode=block', 25 | 'x-github-request-id': 'AE1408AB:EA59:14F2183:57653568' }); 26 | return scope; 27 | } 28 | -------------------------------------------------------------------------------- /test/fixtures/user.json: -------------------------------------------------------------------------------- 1 | { 2 | "USERNAME": "mikedeboertest", 3 | "PASSWORD": "test1324", 4 | "REPO": "github", 5 | "ORGANIZATION": "github-api-tests" 6 | } 7 | -------------------------------------------------------------------------------- /test/gist.spec.js: -------------------------------------------------------------------------------- 1 | import expect from 'must'; 2 | 3 | import Github from '../lib/GitHub'; 4 | import testUser from './fixtures/user.json'; 5 | import testGist from './fixtures/gist.json'; 6 | import {assertSuccessful} from './helpers/callbacks'; 7 | 8 | describe('Gist', function() { 9 | let gist; 10 | let gistId; 11 | let github; 12 | let commentId; 13 | 14 | before(function() { 15 | github = new Github({ 16 | username: testUser.USERNAME, 17 | password: testUser.PASSWORD, 18 | auth: 'basic' 19 | }); 20 | }); 21 | 22 | describe('reading', function() { 23 | before(function() { 24 | gist = github.getGist('f1c0f84e53aa6b98ec03'); 25 | }); 26 | 27 | it('should read a gist', function(done) { 28 | gist.read(assertSuccessful(done, function(err, gist) { 29 | expect(gist).to.have.own('description', testGist.description); 30 | expect(gist.files).to.have.keys(Object.keys(testGist.files)); 31 | expect(gist.files['README.md']).to.have.own('content', testGist.files['README.md'].content); 32 | 33 | done(); 34 | })); 35 | }); 36 | }); 37 | 38 | describe('creating/modifiying', function() { 39 | before(function() { 40 | gist = github.getGist(); 41 | }); 42 | 43 | // 200ms between tests so that Github has a chance to settle 44 | beforeEach(function(done) { 45 | setTimeout(done, 200); 46 | }); 47 | 48 | it('should create gist', function(done) { 49 | gist.create(testGist, assertSuccessful(done, function(err, gist) { 50 | expect(gist).to.have.own('id'); 51 | expect(gist).to.have.own('public', testGist.public); 52 | expect(gist).to.have.own('description', testGist.description); 53 | gistId = gist.id; 54 | 55 | done(); 56 | })); 57 | }); 58 | 59 | it('should star a gist', function(done) { 60 | gist = github.getGist(gistId); 61 | gist.star(assertSuccessful(done, function() { 62 | gist.isStarred(assertSuccessful(done, function(err, result) { 63 | expect(result).to.be(true); 64 | done(); 65 | })); 66 | })); 67 | }); 68 | 69 | it('should create a comment a gist', function(done) { 70 | gist.createComment('Comment test', assertSuccessful(done, function(err, comment) { 71 | expect(comment).to.have.own('body', 'Comment test'); 72 | commentId = comment.id; 73 | done(); 74 | })); 75 | }); 76 | 77 | it('should list comments', function(done) { 78 | gist.listComments(assertSuccessful(done, function(err, comments) { 79 | expect(comments).to.be.an.array(); 80 | done(); 81 | })); 82 | }); 83 | 84 | it('should get comment', function(done) { 85 | gist.getComment(commentId, assertSuccessful(done, function(err, issue) { 86 | expect(issue).to.have.own('id', commentId); 87 | done(); 88 | })); 89 | }); 90 | 91 | it('should edit comment', function(done) { 92 | const newComment = 'new comment test'; 93 | gist.editComment(commentId, newComment, assertSuccessful(done, function(err, comment) { 94 | expect(comment).to.have.own('body', newComment); 95 | done(); 96 | })); 97 | }); 98 | 99 | it('should delete comment', function(done) { 100 | gist.deleteComment(commentId, assertSuccessful(done)); 101 | }); 102 | }); 103 | 104 | describe('deleting', function() { 105 | before(function() { 106 | gist = github.getGist(gistId); 107 | }); 108 | 109 | // 200ms between tests so that Github has a chance to settle 110 | beforeEach(function(done) { 111 | setTimeout(done, 200); 112 | }); 113 | 114 | it('should delete gist', function(done) { 115 | gist.delete(assertSuccessful(done)); 116 | }); 117 | }); 118 | }); 119 | -------------------------------------------------------------------------------- /test/helpers/callbacks.js: -------------------------------------------------------------------------------- 1 | import expect from 'must'; 2 | 3 | const STANDARD_DELAY = 200; // 200ms between nested calls to the API so things settle 4 | 5 | export function assertSuccessful(done, cb) { 6 | return function successCallback(err, res, xhr) { 7 | try { 8 | expect(err).not.to.exist(err ? (err.response ? err.response.data : err) : 'No error'); 9 | expect(res).to.exist(); 10 | expect(xhr).to.be.an.object(); 11 | 12 | if (cb) { 13 | setTimeout(function delay() { 14 | cb(err, res, xhr); 15 | }, STANDARD_DELAY); 16 | } else { 17 | done(); 18 | } 19 | } catch (e) { 20 | done(e); 21 | } 22 | }; 23 | } 24 | 25 | export function assertFailure(done, cb) { 26 | return function failureCallback(err) { 27 | try { 28 | expect(err).to.exist(); 29 | expect(err).to.have.ownProperty('path'); 30 | expect(err.request).to.exist(); 31 | 32 | if (cb) { 33 | setTimeout(function delay() { 34 | cb(err); 35 | }, STANDARD_DELAY); 36 | } else { 37 | done(); 38 | } 39 | } catch (e) { 40 | done(e); 41 | } 42 | }; 43 | } 44 | 45 | export function assertArray(done) { 46 | return assertSuccessful(done, function isArray(err, result) { 47 | expect(result).to.be.an.array(); 48 | done(); 49 | }); 50 | } 51 | -------------------------------------------------------------------------------- /test/helpers/getTestRepoName.js: -------------------------------------------------------------------------------- 1 | module.exports = function() { 2 | let date = new Date(); 3 | return date.getTime() + '-' + Math.floor(Math.random() * 100000).toString(); 4 | }; 5 | -------------------------------------------------------------------------------- /test/issue.spec.js: -------------------------------------------------------------------------------- 1 | import expect from 'must'; 2 | 3 | import Github from '../lib/GitHub'; 4 | import testUser from './fixtures/user.json'; 5 | import {assertSuccessful} from './helpers/callbacks'; 6 | 7 | describe('Issue', function() { 8 | let github; 9 | let remoteIssues; 10 | 11 | before(function() { 12 | github = new Github({ 13 | username: testUser.USERNAME, 14 | password: testUser.PASSWORD, 15 | auth: 'basic' 16 | }); 17 | 18 | remoteIssues = github.getIssues(testUser.USERNAME, 'TestRepo'); 19 | }); 20 | 21 | describe('reading', function() { 22 | let remoteIssueId; 23 | let milestoneId; 24 | 25 | it('should list issues', function(done) { 26 | remoteIssues.listIssues({}, assertSuccessful(done, function(err, issues) { 27 | expect(issues).to.be.an.array(); 28 | remoteIssueId = issues[0].number; 29 | 30 | done(); 31 | })); 32 | }); 33 | 34 | it('should get issue', function(done) { 35 | remoteIssues.getIssue(remoteIssueId, assertSuccessful(done, function(err, issue) { 36 | expect(issue).to.have.own('number', remoteIssueId); 37 | 38 | done(); 39 | })); 40 | }); 41 | 42 | it('should get issue events', function() { 43 | return remoteIssues.listIssueEvents(remoteIssueId) 44 | .then(function({data: events}) { 45 | expect(events).to.be.an.array(); 46 | }); 47 | }); 48 | 49 | it('should get all milestones', function(done) { 50 | remoteIssues.listMilestones() 51 | .then(({data: milestones}) => { 52 | expect(milestones).to.be.an.array(); 53 | milestoneId = milestones[0].number; 54 | 55 | done(); 56 | }).catch(done); 57 | }); 58 | 59 | it('should get a single milestone', function(done) { 60 | remoteIssues.getMilestone(milestoneId) 61 | .then(({data: milestone}) => { 62 | expect(milestone).to.have.own('title', 'Default Milestone'); 63 | done(); 64 | }).catch(done); 65 | }); 66 | }); 67 | 68 | describe('creating/editing/deleting', function() { 69 | let createdIssueId; 70 | let issueCommentId; 71 | let createdMilestoneId; 72 | 73 | // 200ms between tests so that Github has a chance to settle 74 | beforeEach(function(done) { 75 | setTimeout(done, 200); 76 | }); 77 | 78 | it('should create issue', function(done) { 79 | const newIssue = { 80 | title: 'New issue', 81 | body: 'New issue body' 82 | }; 83 | 84 | remoteIssues.createIssue(newIssue, assertSuccessful(done, function(err, issue) { 85 | createdIssueId = issue.number; 86 | expect(issue).to.have.own('url'); 87 | expect(issue).to.have.own('title', newIssue.title); 88 | expect(issue).to.have.own('body', newIssue.body); 89 | 90 | done(); 91 | })); 92 | }); 93 | 94 | it('should edit issue', function(done) { 95 | const newProps = { 96 | title: 'Edited title', 97 | state: 'closed' 98 | }; 99 | 100 | remoteIssues.editIssue(createdIssueId, newProps, assertSuccessful(done, function(err, issue) { 101 | expect(issue).to.have.own('title', newProps.title); 102 | 103 | done(); 104 | })); 105 | }); 106 | 107 | it('should post issue comment', function(done) { 108 | remoteIssues.createIssueComment(createdIssueId, 'Comment test', assertSuccessful(done, function(err, issue) { 109 | expect(issue).to.have.own('body', 'Comment test'); 110 | 111 | done(); 112 | })); 113 | }); 114 | 115 | it('should list issue comments', function(done) { 116 | remoteIssues.listIssueComments(createdIssueId, assertSuccessful(done, function(err, comments) { 117 | expect(comments).to.be.an.array(); 118 | expect(comments[0]).to.have.own('body', 'Comment test'); 119 | issueCommentId = comments[0].id; 120 | done(); 121 | })); 122 | }); 123 | 124 | it('should get a single issue comment', function(done) { 125 | remoteIssues.getIssueComment(issueCommentId, assertSuccessful(done, function(err, comment) { 126 | expect(comment).to.have.own('body', 'Comment test'); 127 | done(); 128 | })); 129 | }); 130 | 131 | it('should edit issue comment', function(done) { 132 | remoteIssues.editIssueComment(issueCommentId, 'Comment test edited', 133 | assertSuccessful(done, function(err, comment) { 134 | expect(comment).to.have.own('body', 'Comment test edited'); 135 | 136 | done(); 137 | })); 138 | }); 139 | 140 | it('should delete issue comment', function(done) { 141 | remoteIssues.deleteIssueComment(issueCommentId, assertSuccessful(done, function(err, response) { 142 | expect(response).to.be.true(); 143 | 144 | done(); 145 | })); 146 | }); 147 | 148 | it('should create a milestone', function(done) { 149 | let milestone = { 150 | title: 'v42', 151 | description: 'The ultimate version' 152 | }; 153 | 154 | remoteIssues.createMilestone(milestone) 155 | .then(({data: createdMilestone}) => { 156 | expect(createdMilestone).to.have.own('number'); 157 | expect(createdMilestone).to.have.own('title', milestone.title); 158 | 159 | createdMilestoneId = createdMilestone.number; 160 | done(); 161 | }).catch(done); 162 | }); 163 | it('should update a milestone', function(done) { 164 | let milestone = { 165 | description: 'Version 6 * 7' 166 | }; 167 | 168 | expect(createdMilestoneId).to.be.a.number(); 169 | remoteIssues.editMilestone(createdMilestoneId, milestone) 170 | .then(({data: createdMilestone}) => { 171 | expect(createdMilestone).to.have.own('number', createdMilestoneId); 172 | expect(createdMilestone).to.have.own('description', milestone.description); 173 | 174 | done(); 175 | }).catch(done); 176 | }); 177 | it('should delete a milestone', function(done) { 178 | expect(createdMilestoneId).to.be.a.number(); 179 | remoteIssues.deleteMilestone(createdMilestoneId) 180 | .then(({status}) => { 181 | expect(status).to.equal(204); 182 | done(); 183 | }).catch(done); 184 | }); 185 | }); 186 | }); 187 | -------------------------------------------------------------------------------- /test/markdown.spec.js: -------------------------------------------------------------------------------- 1 | import expect from 'must'; 2 | 3 | import Github from '../lib/GitHub'; 4 | import testUser from './fixtures/user.json'; 5 | 6 | describe('Markdown', function() { 7 | let github; 8 | let markdown; 9 | 10 | before(function() { 11 | github = new Github({ 12 | username: testUser.USERNAME, 13 | password: testUser.PASSWORD, 14 | auth: 'basic' 15 | }); 16 | 17 | markdown = github.getMarkdown(); 18 | }); 19 | 20 | it('should convert markdown to html as plain Markdown', function(done) { 21 | const options = { 22 | text: 'Hello world github/linguist#1 **cool**, and #1!' 23 | }; 24 | 25 | markdown.render(options) 26 | .then(function({data: html}) { 27 | expect(html).to.be('

Hello world github/linguist#1 cool, and #1!

\n'); 28 | done(); 29 | }).catch(done); 30 | }); 31 | 32 | it('should convert markdown to html as GFM', function(done) { 33 | const options = { 34 | text: 'Hello world github/linguist#1 **cool**, and #1!', 35 | mode: 'gfm', 36 | context: 'github/gollum' 37 | }; 38 | markdown.render(options) 39 | .then(function({data: html}) { 40 | expect(html).to.be('

Hello world github/linguist#1 cool, and #1!

'); // eslint-disable-line 41 | done(); 42 | }).catch(done); 43 | }); 44 | }); 45 | -------------------------------------------------------------------------------- /test/organization.spec.js: -------------------------------------------------------------------------------- 1 | import expect from 'must'; 2 | 3 | import Github from '../lib/GitHub'; 4 | import testUser from './fixtures/user.json'; 5 | import {assertSuccessful, assertArray} from './helpers/callbacks'; 6 | import getTestRepoName from './helpers/getTestRepoName'; 7 | 8 | describe('Organization', function() { 9 | let github; 10 | const ORG_NAME = 'github-tools'; 11 | const MEMBER_NAME = 'clayreimann'; 12 | 13 | before(function() { 14 | github = new Github({ 15 | username: testUser.USERNAME, 16 | password: testUser.PASSWORD, 17 | auth: 'basic' 18 | }); 19 | 20 | }); 21 | 22 | describe('reading', function() { 23 | let organization; 24 | 25 | before(function() { 26 | organization = github.getOrganization(ORG_NAME); 27 | }); 28 | 29 | it('should show user\'s organisation repos', function(done) { 30 | organization.getRepos(assertArray(done)); 31 | }); 32 | 33 | it('should list the users in the organization', function(done) { 34 | organization.listMembers() 35 | .then(function({data: members}) { 36 | expect(members).to.be.an.array(); 37 | 38 | let hasClayReimann = members.reduce((found, member) => member.login === MEMBER_NAME || found, false); 39 | expect(hasClayReimann).to.be.true(); 40 | 41 | done(); 42 | }).catch(done); 43 | }); 44 | 45 | it('should test for membership', function() { 46 | return organization.isMember(MEMBER_NAME) 47 | .then(function(isMember) { 48 | expect(isMember).to.be.true(); 49 | }); 50 | }); 51 | }); 52 | 53 | describe('creating/updating', function() { 54 | let organization; 55 | const testRepoName = getTestRepoName(); 56 | 57 | before(function() { 58 | organization = github.getOrganization(testUser.ORGANIZATION); 59 | }); 60 | 61 | it('should create an organization repo', function(done) { 62 | const options = { 63 | name: testRepoName, 64 | description: 'test create organization repo', 65 | homepage: 'https://github.com/', 66 | private: false, 67 | has_issues: true, // eslint-disable-line 68 | has_wiki: true, // eslint-disable-line 69 | has_downloads: true // eslint-disable-line 70 | }; 71 | 72 | organization.createRepo(options, assertSuccessful(done, function(err, repo) { 73 | expect(repo.name).to.equal(testRepoName); 74 | expect(repo.full_name).to.equal(`${testUser.ORGANIZATION}/${testRepoName}`); // eslint-disable-line 75 | done(); 76 | })); 77 | }); 78 | 79 | // TODO: The longer this is in place the slower it will get if we don't cleanup random test teams 80 | it('should list the teams in the organization', function() { 81 | return organization.getTeams() 82 | .then(({data}) => { 83 | const hasTeam = data.reduce( 84 | (found, member) => member.slug === 'fixed-test-team-1' || found, 85 | false); 86 | 87 | expect(hasTeam).to.be.true(); 88 | }); 89 | }); 90 | 91 | it('should create an organization team', function(done) { 92 | const options = { 93 | name: testRepoName, 94 | description: 'Created by unit tests', 95 | privacy: 'secret' 96 | }; 97 | 98 | organization.createTeam(options, assertSuccessful(done, function(err, team) { 99 | expect(team.name).to.equal(testRepoName); 100 | expect(team.organization.login).to.equal(testUser.ORGANIZATION); // jscs:ignore 101 | done(); 102 | })); 103 | }); 104 | }); 105 | }); 106 | -------------------------------------------------------------------------------- /test/rate-limit.spec.js: -------------------------------------------------------------------------------- 1 | import expect from 'must'; 2 | 3 | import Github from '../lib/GitHub'; 4 | import testUser from './fixtures/user.json'; 5 | import {assertSuccessful} from './helpers/callbacks'; 6 | 7 | describe('RateLimit', function() { 8 | let github; 9 | let rateLimit; 10 | 11 | before(function() { 12 | github = new Github({ 13 | username: testUser.USERNAME, 14 | password: testUser.PASSWORD, 15 | auth: 'basic' 16 | }); 17 | 18 | rateLimit = github.getRateLimit(); 19 | }); 20 | 21 | it('should get rate limit', function(done) { 22 | rateLimit.getRateLimit(assertSuccessful(done, function(err, rateInfo) { 23 | const rate = rateInfo.rate; 24 | 25 | expect(rate).to.be.an.object(); 26 | expect(rate).to.have.own('limit'); 27 | expect(rate).to.have.own('remaining'); 28 | expect(rate.limit).to.be.a.number(); 29 | expect(rate.remaining).to.be.a.number(); 30 | expect(rate.remaining).to.be.at.most(rateInfo.rate.limit); 31 | 32 | done(); 33 | })); 34 | }); 35 | }); 36 | -------------------------------------------------------------------------------- /test/repository.spec.js: -------------------------------------------------------------------------------- 1 | import expect from 'must'; 2 | 3 | import Github from '../lib/GitHub'; 4 | import testUser from './fixtures/user.json'; 5 | import loadImage from './fixtures/imageBlob'; 6 | import {assertSuccessful, assertFailure} from './helpers/callbacks'; 7 | import getTestRepoName from './helpers/getTestRepoName'; 8 | 9 | describe('Repository', function() { 10 | let github; 11 | let remoteRepo; 12 | let user; 13 | let imageB64; 14 | let imageBlob; 15 | const testRepoName = getTestRepoName(); 16 | const v10SHA = '20fcff9129005d14cc97b9d59b8a3d37f4fb633b'; 17 | const statusUrl = 'https://api.github.com/repos/michael/github/statuses/20fcff9129005d14cc97b9d59b8a3d37f4fb633b'; 18 | 19 | before(function(done) { 20 | github = new Github({ 21 | username: testUser.USERNAME, 22 | password: testUser.PASSWORD 23 | }); 24 | 25 | loadImage(function(b64, blob) { 26 | imageB64 = b64; 27 | imageBlob = blob; 28 | done(); 29 | }); 30 | }); 31 | 32 | describe('reading', function() { 33 | before(function() { 34 | remoteRepo = github.getRepo('michael', 'github'); 35 | }); 36 | 37 | it('should get repo details', function(done) { 38 | remoteRepo.getDetails(assertSuccessful(done, function(err, repo) { 39 | expect(repo).to.have.own('full_name', 'michael/github'); 40 | 41 | done(); 42 | })); 43 | }); 44 | 45 | it('should get blob', function(done) { 46 | remoteRepo.getSha('master', 'README.md', assertSuccessful(done, function(err, response) { 47 | remoteRepo.getBlob(response.sha, assertSuccessful(done, function(err, content) { 48 | expect(content).to.be.include('# Github.js'); 49 | 50 | done(); 51 | })); 52 | })); 53 | }); 54 | 55 | it('should get a branch', function(done) { 56 | remoteRepo.getBranch('master', assertSuccessful(done, function(err, content) { 57 | expect(content.name).to.be('master'); 58 | 59 | done(); 60 | })); 61 | }); 62 | 63 | it('should show repo contents', function(done) { 64 | remoteRepo.getContents('master', '', false, assertSuccessful(done, function(err, contents) { 65 | expect(contents).to.be.an.array(); 66 | 67 | const readme = contents.filter(function(content) { 68 | return content.path === 'README.md'; 69 | }); 70 | 71 | expect(readme).to.have.length(1); 72 | expect(readme[0]).to.have.own('type', 'file'); 73 | 74 | done(); 75 | })); 76 | }); 77 | 78 | it('should show repo content raw', function(done) { 79 | remoteRepo.getContents('master', 'README.md', 'raw', assertSuccessful(done, function(err, text) { 80 | expect(text).to.contain('# Github.js'); 81 | 82 | done(); 83 | })); 84 | }); 85 | 86 | it('should show repo readme', function(done) { 87 | remoteRepo.getReadme('master', 'raw', assertSuccessful(done, function(err, text) { 88 | expect(text).to.contain('# Github.js'); 89 | 90 | done(); 91 | })); 92 | }); 93 | 94 | it('should get ref from repo', function(done) { 95 | remoteRepo.getRef('heads/master', assertSuccessful(done)); 96 | }); 97 | 98 | it('should get tree', function(done) { 99 | remoteRepo.getTree('master', assertSuccessful(done, function(err, response) { 100 | let {tree} = response; 101 | expect(tree).to.be.an.array(); 102 | expect(tree.length).to.be.above(0); 103 | 104 | done(); 105 | })); 106 | }); 107 | 108 | it('should fork repo', function(done) { 109 | remoteRepo.fork(assertSuccessful(done)); 110 | }); 111 | 112 | it('should list forks of repo', function(done) { 113 | remoteRepo.listForks(assertSuccessful(done, function(err, forks) { 114 | expect(forks).to.be.an.array(); 115 | expect(forks.length).to.be.above(0); 116 | done(); 117 | })); 118 | }); 119 | 120 | it('should list commits with no options', function(done) { 121 | remoteRepo.listCommits(null, assertSuccessful(done, function(err, commits) { 122 | expect(commits).to.be.an.array(); 123 | expect(commits.length).to.be.above(0); 124 | 125 | expect(commits[0]).to.have.own('commit'); 126 | expect(commits[0]).to.have.own('author'); 127 | 128 | done(); 129 | })); 130 | }); 131 | 132 | it('should list commits with all options', function(done) { 133 | const since = new Date(2015, 0, 1); 134 | const until = new Date(2016, 0, 20); 135 | const options = { 136 | sha: 'master', 137 | path: 'test', 138 | author: 'AurelioDeRosa', 139 | since, 140 | until 141 | }; 142 | 143 | remoteRepo.listCommits(options, assertSuccessful(done, function(err, commits) { 144 | expect(commits).to.be.an.array(); 145 | expect(commits.length).to.be.above(0); 146 | 147 | const commit = commits[0]; 148 | const commitDate = new Date(commit.commit.author.date); 149 | 150 | expect(commit).to.have.own('commit'); 151 | expect(commit.author).to.have.own('login', 'AurelioDeRosa'); 152 | expect(commitDate.getTime()).to.be.between(since.getTime(), until.getTime()); 153 | done(); 154 | })); 155 | }); 156 | 157 | it('should get the latest commit from master', function(done) { 158 | remoteRepo.getSingleCommit('master', assertSuccessful(done, function(err, commit) { 159 | expect(commit).to.have.own('sha'); 160 | expect(commit).to.have.own('commit'); 161 | expect(commit).to.have.own('author'); 162 | 163 | done(); 164 | })); 165 | }); 166 | 167 | it('should fail when null ref is passed', function(done) { 168 | remoteRepo.getSingleCommit(null, assertFailure(done, function(err) { 169 | expect(err.status).to.be(404); 170 | done(); 171 | })); 172 | }); 173 | 174 | it('should show repo contributors', function(done) { 175 | remoteRepo.getContributors(assertSuccessful(done, function(err, contributors) { 176 | if (!(contributors instanceof Array)) { 177 | console.log(JSON.stringify(contributors, null, 2)); // eslint-disable-line 178 | } 179 | expect(contributors).to.be.an.array(); 180 | expect(contributors.length).to.be.above(1); 181 | 182 | const contributor = contributors[0]; 183 | 184 | expect(contributor).to.have.own('author'); 185 | expect(contributor).to.have.own('total'); 186 | expect(contributor).to.have.own('weeks'); 187 | 188 | done(); 189 | })); 190 | }); 191 | 192 | // @TODO repo.branch, repo.pull 193 | 194 | it('should list repo branches', function(done) { 195 | remoteRepo.listBranches(assertSuccessful(done)); 196 | }); 197 | 198 | it('should get commit from repo', function(done) { 199 | remoteRepo.getCommit(v10SHA, assertSuccessful(done, function(err, commit) { 200 | expect(commit.message).to.equal('v0.10.4'); 201 | expect(commit.author.date).to.equal('2015-03-20T17:01:42Z'); 202 | 203 | done(); 204 | })); 205 | }); 206 | 207 | it('should get statuses for a SHA from a repo', function(done) { 208 | remoteRepo.listStatuses(v10SHA, assertSuccessful(done, function(err, statuses) { 209 | expect(statuses).to.be.an.array(); 210 | expect(statuses.length).to.equal(6); 211 | 212 | const correctUrls = statuses.every(function(status) { 213 | return status.url === statusUrl; 214 | }); 215 | 216 | expect(correctUrls).to.be(true); 217 | 218 | done(); 219 | })); 220 | }); 221 | 222 | it('should get a SHA from a repo', function(done) { 223 | remoteRepo.getSha('master', '.gitignore', assertSuccessful(done)); 224 | }); 225 | 226 | it('should get a repo by fullname', function(done) { 227 | const repoByName = github.getRepo('michael/github'); 228 | 229 | repoByName.getDetails(assertSuccessful(done, function(err, repo) { 230 | expect(repo).to.have.own('full_name', 'michael/github'); 231 | 232 | done(); 233 | })); 234 | }); 235 | 236 | it('should check if the repo is starred', function(done) { 237 | remoteRepo.isStarred(assertSuccessful(done, function(err, result) { 238 | expect(result).to.equal(false); 239 | 240 | done(); 241 | })); 242 | }); 243 | }); 244 | 245 | describe('creating/modifiying', function() { 246 | const fileName = 'test.md'; 247 | 248 | const initialText = 'This is a test.'; 249 | const initialMessage = 'This is my 44 character long commit message.'; 250 | 251 | const updatedText = 'This file has been updated.'; 252 | const updatedMessage = 'This is my 51 character long update commit message.'; 253 | 254 | const fileToDelete = 'tmp.md'; 255 | const deleteMessage = 'This is my 51 character long delete commit message.'; 256 | 257 | const unicodeFileName = '\u4E2D\u6587\u6D4B\u8BD5.md'; 258 | const unicodeText = '\u00A1\u00D3i de m\u00ED, que verg\u00FCenza!'; 259 | const unicodeMessage = 'Such na\u00EFvet\u00E9\u2026'; 260 | 261 | const imageFileName = 'image.png'; 262 | 263 | const releaseTag = 'foo'; 264 | const releaseName = 'My awesome release'; 265 | const releaseBody = 'This is my 49 character long release description.'; 266 | let sha; 267 | let releaseId; 268 | 269 | before(function() { 270 | user = github.getUser(); 271 | remoteRepo = github.getRepo(testUser.USERNAME, testRepoName); 272 | }); 273 | 274 | // 200ms between tests so that Github has a chance to settle 275 | beforeEach(function(done) { 276 | setTimeout(done, 200); 277 | }); 278 | 279 | it('should create repo', function(done) { 280 | const repoDef = { 281 | name: testRepoName 282 | }; 283 | 284 | user.createRepo(repoDef, assertSuccessful(done, function(err, repo) { 285 | expect(repo).to.have.own('name', testRepoName); 286 | 287 | done(); 288 | })); 289 | }); 290 | 291 | it('should show repo collaborators', function(done) { 292 | remoteRepo.getCollaborators(assertSuccessful(done, function(err, collaborators) { 293 | if (!(collaborators instanceof Array)) { 294 | console.log(JSON.stringify(collaborators, null, 2)); // eslint-disable-line 295 | } 296 | expect(collaborators).to.be.an.array(); 297 | expect(collaborators).to.have.length(1); 298 | 299 | const collaborator = collaborators[0]; 300 | 301 | expect(collaborator).to.have.own('login', testUser.USERNAME); 302 | expect(collaborator).to.have.own('id'); 303 | expect(collaborator).to.have.own('permissions'); 304 | 305 | done(); 306 | })); 307 | }); 308 | 309 | it('should test whether user is collaborator', function(done) { 310 | remoteRepo.isCollaborator(testUser.USERNAME, assertSuccessful(done)); 311 | }); 312 | 313 | it('should write to repo', function(done) { 314 | remoteRepo.writeFile('master', fileName, initialText, initialMessage, assertSuccessful(done, function() { 315 | remoteRepo.getContents('master', fileName, 'raw', assertSuccessful(done, function(err, fileText) { 316 | expect(fileText).to.be(initialText); 317 | 318 | done(); 319 | })); 320 | })); 321 | }); 322 | 323 | it('should rename files', function(done) { 324 | remoteRepo.writeFile('master', fileName, initialText, initialMessage, assertSuccessful(done, function() { 325 | remoteRepo.move('master', fileName, 'new_name', assertSuccessful(done, function() { 326 | remoteRepo.getContents('master', fileName, 'raw', assertFailure(done, function(err) { 327 | expect(err.status).to.be(404); 328 | remoteRepo.getContents('master', 'new_name', 'raw', assertSuccessful(done, function(err, fileText) { 329 | expect(fileText).to.be(initialText); 330 | 331 | done(); 332 | })); 333 | })); 334 | })); 335 | })); 336 | }); 337 | 338 | it('should create a new branch', function(done) { 339 | remoteRepo.createBranch('master', 'dev', assertSuccessful(done, function(err, branch) { 340 | expect(branch).to.have.property('ref', 'refs/heads/dev'); 341 | expect(branch.object).to.have.property('sha'); 342 | 343 | done(); 344 | })); 345 | }); 346 | 347 | it('should write to repo branch', function(done) { 348 | remoteRepo.writeFile('dev', fileName, updatedText, updatedMessage, assertSuccessful(done, function() { 349 | remoteRepo.getContents('dev', fileName, 'raw', assertSuccessful(done, function(err, fileText) { 350 | expect(fileText).to.be(updatedText); 351 | 352 | done(); 353 | })); 354 | })); 355 | }); 356 | 357 | it('should compare two branches', function(done) { 358 | remoteRepo.createBranch('master', 'compare', assertSuccessful(done, function() { 359 | remoteRepo.writeFile('compare', fileName, updatedText, updatedMessage, assertSuccessful(done, function() { 360 | remoteRepo.compareBranches('master', 'compare', assertSuccessful(done, function(err, diff) { 361 | expect(diff).to.have.own('total_commits', 1); 362 | expect(diff.files[0]).to.have.own('filename', fileName); 363 | 364 | done(); 365 | })); 366 | })); 367 | })); 368 | }); 369 | 370 | it('should submit a pull request', function(done) { 371 | const base = 'master'; 372 | const head = 'pull-request'; 373 | const title = 'Test pull request'; 374 | const body = 'This is a test pull request'; 375 | const prSpec = {title, body, base, head}; 376 | 377 | remoteRepo.createBranch(base, head, assertSuccessful(done, function() { 378 | remoteRepo.writeFile(head, fileName, updatedText, updatedMessage, assertSuccessful(done, function() { 379 | remoteRepo.createPullRequest(prSpec, assertSuccessful(done, function(err, pullRequest) { 380 | expect(pullRequest).to.have.own('number'); 381 | expect(pullRequest.number).to.be.above(0); 382 | expect(pullRequest).to.have.own('title', title); 383 | expect(pullRequest).to.have.own('body', body); 384 | 385 | done(); 386 | })); 387 | })); 388 | })); 389 | }); 390 | 391 | it('should create ref on repo', function(done) { 392 | remoteRepo.getRef('heads/master', assertSuccessful(done, function(err, refSpec) { 393 | let newRef = { 394 | ref: 'refs/heads/new-test-branch', 395 | sha: refSpec.object.sha 396 | }; 397 | remoteRepo.createRef(newRef, assertSuccessful(done)); 398 | })); 399 | }); 400 | 401 | it('should delete ref on repo', function(done) { 402 | remoteRepo.deleteRef('heads/new-test-branch', assertSuccessful(done)); 403 | }); 404 | 405 | it('should list tags on repo', function(done) { 406 | remoteRepo.listTags(assertSuccessful(done)); 407 | }); 408 | 409 | it('should list pulls on repo', function(done) { 410 | const filterOpts = { 411 | state: 'all', 412 | sort: 'updated', 413 | direction: 'desc', 414 | page: 1, 415 | per_page: 10 //eslint-disable-line 416 | }; 417 | 418 | remoteRepo.listPullRequests(filterOpts, assertSuccessful(done, function(err, pullRequests) { 419 | expect(pullRequests).to.be.an.array(); 420 | expect(pullRequests).to.have.length(1); 421 | 422 | done(); 423 | })); 424 | }); 425 | 426 | it('should get pull requests on repo', function(done) { 427 | const repo = github.getRepo('michael', 'github'); 428 | 429 | repo.getPullRequest(153, assertSuccessful(done, function(err, pr) { 430 | expect(pr).to.have.own('title'); 431 | expect(pr).to.have.own('body'); 432 | expect(pr).to.have.own('url'); 433 | 434 | done(); 435 | })); 436 | }); 437 | 438 | it('should delete a file on the repo', function(done) { 439 | remoteRepo.writeFile('master', fileToDelete, initialText, deleteMessage, assertSuccessful(done, function() { 440 | remoteRepo.deleteFile('master', fileToDelete, assertSuccessful(done)); 441 | })); 442 | }); 443 | 444 | it('should write author and committer to repo', function(done) { 445 | const options = { 446 | author: {name: 'Author Name', email: 'author@example.com'}, 447 | committer: {name: 'Committer Name', email: 'committer@example.com'} 448 | }; 449 | 450 | remoteRepo.writeFile('dev', 451 | fileName, initialText, initialMessage, options, 452 | assertSuccessful(done, function(error, commit) { 453 | remoteRepo.getCommit(commit.commit.sha, assertSuccessful(done, function(err, commit2) { 454 | const author = commit2.author; 455 | const committer = commit2.committer; 456 | expect(author.name).to.be('Author Name'); 457 | expect(author.email).to.be('author@example.com'); 458 | expect(committer.name).to.be('Committer Name'); 459 | expect(committer.email).to.be('committer@example.com'); 460 | 461 | done(); 462 | })); 463 | }) 464 | ); 465 | }); 466 | 467 | it('should be able to write all the unicode', function(done) { 468 | remoteRepo.writeFile('master', unicodeFileName, unicodeText, unicodeMessage, assertSuccessful(done, 469 | function(err, commit) { 470 | expect(commit.content.name).to.be(unicodeFileName); 471 | expect(commit.commit.message).to.be(unicodeMessage); 472 | 473 | remoteRepo.getContents('master', unicodeFileName, 'raw', assertSuccessful(done, function(err, fileText) { 474 | expect(fileText).to.be(unicodeText); 475 | 476 | done(); 477 | })); 478 | })); 479 | }); 480 | 481 | it('should pass a regression test for _request (#14)', function(done) { 482 | remoteRepo.getRef('heads/master', assertSuccessful(done, function(err, refSpec) { 483 | let newRef = { 484 | ref: 'refs/heads/testing-14', 485 | sha: refSpec.object.sha 486 | }; 487 | 488 | remoteRepo.createRef(newRef, assertSuccessful(done, function() { 489 | // Triggers GET: 490 | // https://api.github.com/repos/michael/cmake_cdt7_stalled/git/refs/heads/prose-integration 491 | remoteRepo.getRef('heads/master', assertSuccessful(done, function() { 492 | // Triggers DELETE: 493 | // https://api.github.com/repos/michael/cmake_cdt7_stalled/git/refs/heads/prose-integration 494 | remoteRepo.deleteRef('heads/testing-14', assertSuccessful(done, function(err, res, xhr) { 495 | expect(xhr.status).to.be(204); 496 | 497 | done(); 498 | })); 499 | })); 500 | })); 501 | })); 502 | }); 503 | 504 | it('should be able to write an image to the repo', function(done) { 505 | const opts = { 506 | encode: false 507 | }; 508 | 509 | remoteRepo.writeFile('master', imageFileName, imageB64, initialMessage, opts, assertSuccessful(done, 510 | function(err, commit) { 511 | sha = commit.sha; 512 | 513 | done(); 514 | })); 515 | }); 516 | 517 | it('should be able to write a string blob to the repo', function(done) { 518 | remoteRepo.createBlob('String test', assertSuccessful(done)); 519 | }); 520 | 521 | it('should be able to write a file blob to the repo', function(done) { 522 | remoteRepo.createBlob(imageBlob, assertSuccessful(done)); 523 | }); 524 | 525 | it('should star the repo', function(done) { 526 | remoteRepo.star(assertSuccessful(done, function() { 527 | remoteRepo.isStarred(assertSuccessful(done)); 528 | })); 529 | }); 530 | 531 | it('should unstar the repo', function(done) { 532 | remoteRepo.unstar(assertSuccessful(done, function() { 533 | remoteRepo.isStarred(assertSuccessful(done, function(_, isStarred) { 534 | expect(isStarred).to.be(false); 535 | done(); 536 | })); 537 | })); 538 | }); 539 | 540 | it('should fail on broken commit', function(done) { 541 | remoteRepo.commit('broken-parent-hash', 'broken-tree-hash', initialMessage, assertFailure(done, function(err) { 542 | expect(err.status).to.be(422); 543 | done(); 544 | })); 545 | }); 546 | 547 | it('should create a release', function(done) { 548 | const releaseDef = { 549 | name: releaseName, 550 | tag_name: releaseTag, // eslint-disable-line 551 | target_commitish: sha // eslint-disable-line 552 | }; 553 | 554 | remoteRepo.createRelease(releaseDef, assertSuccessful(done, function(err, res) { 555 | releaseId = res.id; 556 | done(); 557 | })); 558 | }); 559 | 560 | it('should edit a release', function(done) { 561 | const releaseDef = { 562 | name: releaseName, 563 | body: releaseBody 564 | }; 565 | 566 | remoteRepo.updateRelease(releaseId, releaseDef, assertSuccessful(done, function(err, release) { 567 | expect(release).to.have.own('name', releaseName); 568 | expect(release).to.have.own('body', releaseBody); 569 | 570 | done(); 571 | })); 572 | }); 573 | 574 | it('should read all releases', function(done) { 575 | remoteRepo.listReleases(assertSuccessful(done, function(err, releases) { 576 | expect(releases).to.be.an.array(); 577 | done(); 578 | })); 579 | }); 580 | 581 | it('should read a release', function(done) { 582 | remoteRepo.getRelease(releaseId, assertSuccessful(done, function(err, release) { 583 | expect(release).to.have.own('name', releaseName); 584 | 585 | done(); 586 | })); 587 | }); 588 | 589 | it('should delete a release', function(done) { 590 | remoteRepo.deleteRelease(releaseId, assertSuccessful(done)); 591 | }); 592 | }); 593 | 594 | describe('deleting', function() { 595 | before(function() { 596 | remoteRepo = github.getRepo(testUser.USERNAME, testRepoName); 597 | }); 598 | 599 | // 200ms between tests so that Github has a chance to settle 600 | beforeEach(function(done) { 601 | setTimeout(done, 200); 602 | }); 603 | 604 | it('should delete the repo', function(done) { 605 | remoteRepo.deleteRepo(assertSuccessful(done, function(err, result) { 606 | expect(result).to.be(true); 607 | 608 | done(); 609 | })); 610 | }); 611 | }); 612 | }); 613 | -------------------------------------------------------------------------------- /test/search.spec.js: -------------------------------------------------------------------------------- 1 | import expect from 'must'; 2 | import nock from 'nock'; 3 | 4 | import Github from '../lib/GitHub'; 5 | import testUser from './fixtures/user.json'; 6 | 7 | describe('Search', function() { 8 | this.timeout(20 * 1000); 9 | let github; 10 | 11 | before(function() { 12 | github = new Github({ 13 | username: testUser.USERNAME, 14 | password: testUser.PASSWORD, 15 | auth: 'basic' 16 | }); 17 | nock.load('test/fixtures/search.json'); 18 | }); 19 | 20 | it('should search repositories', function() { 21 | let options; 22 | let search = github.search({ 23 | q: 'tetris language:assembly', 24 | sort: 'stars', 25 | order: 'desc' 26 | }); 27 | 28 | return search.forRepositories(options) 29 | .then(function({data}) { 30 | expect(data).to.be.an.array(); 31 | expect(data.length).to.be.above(0); 32 | }); 33 | }); 34 | 35 | it('should search code', function() { 36 | let options; 37 | let search = github.search({ 38 | q: 'addClass in:file language:js repo:jquery/jquery' 39 | }); 40 | 41 | return search.forCode(options) 42 | .then(function({data}) { 43 | expect(data).to.be.an.array(); 44 | expect(data.length).to.be.above(0); 45 | }); 46 | }); 47 | 48 | it('should search issues', function() { 49 | let options; 50 | let search = github.search({ 51 | q: 'windows pip label:bug language:python state:open ', 52 | sort: 'created', 53 | order: 'asc' 54 | }); 55 | 56 | return search.forIssues(options) 57 | .then(function({data}) { 58 | expect(data).to.be.an.array(); 59 | expect(data.length).to.be.above(0); 60 | }); 61 | }); 62 | 63 | it('should search users', function() { 64 | let options; 65 | let search = github.search({ 66 | q: 'tom repos:>42 followers:>1000' 67 | }); 68 | 69 | return search.forUsers(options) 70 | .then(function({data}) { 71 | expect(data).to.be.an.array(); 72 | expect(data.length).to.be.above(0); 73 | }); 74 | }); 75 | 76 | after(function() { 77 | nock.cleanAll(); 78 | nock.restore(); 79 | }); 80 | }); 81 | -------------------------------------------------------------------------------- /test/team.spec.js: -------------------------------------------------------------------------------- 1 | import expect from 'must'; 2 | 3 | import Github from '../lib/GitHub'; 4 | import testUser from './fixtures/user.json'; 5 | import {assertFailure} from './helpers/callbacks'; 6 | import getTestRepoName from './helpers/getTestRepoName'; 7 | 8 | const altUser = { 9 | USERNAME: 'mtscout6-test' 10 | }; 11 | 12 | function createTestTeam() { 13 | const name = getTestRepoName(); 14 | 15 | const github = new Github({ 16 | username: testUser.USERNAME, 17 | password: testUser.PASSWORD, 18 | auth: 'basic' 19 | }); 20 | 21 | const org = github.getOrganization(testUser.ORGANIZATION); 22 | 23 | return org.createTeam({ 24 | name, 25 | privacy: 'closed' 26 | }).then(({data: result}) => { 27 | const team = github.getTeam(result.id); 28 | return {team, name}; 29 | }); 30 | } 31 | 32 | let team; 33 | let name; 34 | 35 | describe('Team', function() { // Isolate tests that are based on a fixed team 36 | before(function() { 37 | const github = new Github({ 38 | username: testUser.USERNAME, 39 | password: testUser.PASSWORD, 40 | auth: 'basic' 41 | }); 42 | 43 | team = github.getTeam(2027812); // github-api-tests/fixed-test-team-1 44 | }); 45 | 46 | it('should get membership for a given user', function() { 47 | return team.getMembership(altUser.USERNAME) 48 | .then(function({data}) { 49 | expect(data.state).to.equal('active'); 50 | expect(data.role).to.equal('member'); 51 | }); 52 | }); 53 | 54 | it('should list the users in the team', function() { 55 | return team.listMembers() 56 | .then(function({data: members}) { 57 | expect(members).to.be.an.array(); 58 | 59 | let hasTestUser = members.reduce( 60 | (found, member) => member.login === testUser.USERNAME || found, 61 | false 62 | ); 63 | 64 | expect(hasTestUser).to.be.true(); 65 | }); 66 | }); 67 | 68 | it('should get team repos', function() { 69 | return team.listRepos() 70 | .then(({data}) => { 71 | const hasRepo = data.reduce( 72 | (found, repo) => repo.name === 'fixed-test-repo-1' || found, 73 | false 74 | ); 75 | 76 | expect(hasRepo).to.be.true(); 77 | }); 78 | }); 79 | 80 | it('should get team', function() { 81 | return team.getTeam() 82 | .then(({data}) => { 83 | expect(data.name).to.equal('Fixed Test Team 1'); 84 | }); 85 | }); 86 | 87 | it('should check if team manages repo', function() { 88 | return team.isManagedRepo(testUser.ORGANIZATION, 'fixed-test-repo-1') 89 | .then((result) => { 90 | expect(result).to.be.true(); 91 | }); 92 | }); 93 | }); 94 | 95 | describe('Team', function() { // Isolate tests that need a new team per test 96 | beforeEach(function() { 97 | return createTestTeam() 98 | .then((x) => { 99 | team = x.team; 100 | name = x.name; 101 | }); 102 | }); 103 | 104 | // Test for Team deletion 105 | afterEach(function(done) { 106 | team.deleteTeam() 107 | .then(() => team.getTeam(assertFailure(done))); 108 | }); 109 | 110 | it('should update team', function() { 111 | const newName = `${name}-updated`; 112 | return team.editTeam({name: newName}) 113 | .then(function({data}) { 114 | expect(data.name).to.equal(newName); 115 | }); 116 | }); 117 | 118 | it('should add membership for a given user', function() { 119 | return team.addMembership(testUser.USERNAME) 120 | .then(({data}) => { 121 | const {state, role} = data; 122 | expect(state === 'active' || state === 'pending').to.be.true(); 123 | expect(role).to.equal('member'); 124 | }); 125 | }); 126 | 127 | it('should add membership as a maintainer for a given user', function() { 128 | return team.addMembership(altUser.USERNAME, {role: 'maintainer'}) 129 | .then(({data}) => { 130 | const {state, role} = data; 131 | expect(state === 'active' || state === 'pending').to.be.true(); 132 | expect(role).to.equal('maintainer'); 133 | }); 134 | }); 135 | 136 | it('should add/remove team management of repo', function() { 137 | return team.manageRepo(testUser.ORGANIZATION, 'fixed-test-repo-1', {permission: 'pull'}) 138 | .then((result) => { 139 | expect(result).to.be.true(); 140 | return team.unmanageRepo(testUser.ORGANIZATION, 'fixed-test-repo-1'); 141 | }) 142 | .then((result) => { 143 | expect(result).to.be.true(); 144 | }); 145 | }); 146 | }); 147 | -------------------------------------------------------------------------------- /test/user.spec.js: -------------------------------------------------------------------------------- 1 | import Github from '../lib/GitHub'; 2 | import testUser from './fixtures/user.json'; 3 | import {assertSuccessful, assertArray} from './helpers/callbacks'; 4 | 5 | describe('User', function() { 6 | let github; 7 | let user; 8 | 9 | before(function() { 10 | github = new Github({ 11 | username: testUser.USERNAME, 12 | password: testUser.PASSWORD, 13 | auth: 'basic' 14 | }); 15 | user = github.getUser(); 16 | }); 17 | 18 | it('should get user repos', function(done) { 19 | user.listRepos(assertArray(done)); 20 | }); 21 | 22 | it('should get user repos with options', function(done) { 23 | const filterOpts = { 24 | type: 'owner', 25 | sort: 'updated', 26 | per_page: 90, // eslint-disable-line 27 | page: 10 28 | }; 29 | 30 | user.listRepos(filterOpts, assertArray(done)); 31 | }); 32 | 33 | it('should get user orgs', function(done) { 34 | user.listOrgs(assertArray(done)); 35 | }); 36 | 37 | it('should get user gists', function(done) { 38 | user.listGists(assertArray(done)); 39 | }); 40 | 41 | it('should get user notifications', function(done) { 42 | user.listNotifications(assertArray(done)); 43 | }); 44 | 45 | it('should get user notifications with options', function(done) { 46 | const filterOpts = { 47 | all: true, 48 | participating: true, 49 | since: '2015-01-01T00:00:00Z', 50 | before: '2015-02-01T00:00:00Z' 51 | }; 52 | 53 | user.listNotifications(filterOpts, assertArray(done)); 54 | }); 55 | 56 | it('should get the user\'s profile', function(done) { 57 | user.getProfile(assertSuccessful(done)); 58 | }); 59 | 60 | it('should show user\'s starred repos', function(done) { 61 | user.listStarredRepos(assertArray(done)); 62 | }); 63 | 64 | it('should follow user', function(done) { 65 | user.follow('ingalls', assertSuccessful(done)); 66 | }); 67 | 68 | it('should unfollow user', function(done) { 69 | user.unfollow('ingalls', assertSuccessful(done)); 70 | }); 71 | }); 72 | -------------------------------------------------------------------------------- /test/vendor/Blob.js: -------------------------------------------------------------------------------- 1 | /* Blob.js 2 | * A Blob implementation. 3 | * 2014-07-24 4 | * 5 | * By Eli Grey, http://eligrey.com 6 | * By Devin Samarin, https://github.com/dsamarin 7 | * License: MIT 8 | * See https://github.com/eligrey/Blob.js/blob/master/LICENSE.md 9 | */ 10 | 11 | /*global self, unescape */ 12 | /*jslint bitwise: true, regexp: true, confusion: true, es5: true, vars: true, white: true, 13 | plusplus: true */ 14 | 15 | /*! @source http://purl.eligrey.com/github/Blob.js/blob/master/Blob.js */ 16 | 17 | (function (view) { 18 | "use strict"; 19 | 20 | view.URL = view.URL || view.webkitURL; 21 | 22 | if (view.Blob && view.URL) { 23 | try { 24 | new Blob; 25 | return; 26 | } catch (e) {} 27 | } 28 | 29 | // Internally we use a BlobBuilder implementation to base Blob off of 30 | // in order to support older browsers that only have BlobBuilder 31 | var BlobBuilder = view.BlobBuilder || view.WebKitBlobBuilder || view.MozBlobBuilder || (function(view) { 32 | var 33 | get_class = function(object) { 34 | return Object.prototype.toString.call(object).match(/^\[object\s(.*)\]$/)[1]; 35 | } 36 | , FakeBlobBuilder = function BlobBuilder() { 37 | this.data = []; 38 | } 39 | , FakeBlob = function Blob(data, type, encoding) { 40 | this.data = data; 41 | this.size = data.length; 42 | this.type = type; 43 | this.encoding = encoding; 44 | } 45 | , FBB_proto = FakeBlobBuilder.prototype 46 | , FB_proto = FakeBlob.prototype 47 | , FileReaderSync = view.FileReaderSync 48 | , FileException = function(type) { 49 | this.code = this[this.name = type]; 50 | } 51 | , file_ex_codes = ( 52 | "NOT_FOUND_ERR SECURITY_ERR ABORT_ERR NOT_READABLE_ERR ENCODING_ERR " 53 | + "NO_MODIFICATION_ALLOWED_ERR INVALID_STATE_ERR SYNTAX_ERR" 54 | ).split(" ") 55 | , file_ex_code = file_ex_codes.length 56 | , real_URL = view.URL || view.webkitURL || view 57 | , real_create_object_URL = real_URL.createObjectURL 58 | , real_revoke_object_URL = real_URL.revokeObjectURL 59 | , URL = real_URL 60 | , btoa = view.btoa 61 | , atob = view.atob 62 | 63 | , ArrayBuffer = view.ArrayBuffer 64 | , Uint8Array = view.Uint8Array 65 | 66 | , origin = /^[\w-]+:\/*\[?[\w\.:-]+\]?(?::[0-9]+)?/ 67 | ; 68 | FakeBlob.fake = FB_proto.fake = true; 69 | while (file_ex_code--) { 70 | FileException.prototype[file_ex_codes[file_ex_code]] = file_ex_code + 1; 71 | } 72 | // Polyfill URL 73 | if (!real_URL.createObjectURL) { 74 | URL = view.URL = function(uri) { 75 | var 76 | uri_info = document.createElementNS("http://www.w3.org/1999/xhtml", "a") 77 | , uri_origin 78 | ; 79 | uri_info.href = uri; 80 | if (!("origin" in uri_info)) { 81 | if (uri_info.protocol.toLowerCase() === "data:") { 82 | uri_info.origin = null; 83 | } else { 84 | uri_origin = uri.match(origin); 85 | uri_info.origin = uri_origin && uri_origin[1]; 86 | } 87 | } 88 | return uri_info; 89 | }; 90 | } 91 | URL.createObjectURL = function(blob) { 92 | var 93 | type = blob.type 94 | , data_URI_header 95 | ; 96 | if (type === null) { 97 | type = "application/octet-stream"; 98 | } 99 | if (blob instanceof FakeBlob) { 100 | data_URI_header = "data:" + type; 101 | if (blob.encoding === "base64") { 102 | return data_URI_header + ";base64," + blob.data; 103 | } else if (blob.encoding === "URI") { 104 | return data_URI_header + "," + decodeURIComponent(blob.data); 105 | } if (btoa) { 106 | return data_URI_header + ";base64," + btoa(blob.data); 107 | } else { 108 | return data_URI_header + "," + encodeURIComponent(blob.data); 109 | } 110 | } else if (real_create_object_URL) { 111 | return real_create_object_URL.call(real_URL, blob); 112 | } 113 | }; 114 | URL.revokeObjectURL = function(object_URL) { 115 | if (object_URL.substring(0, 5) !== "data:" && real_revoke_object_URL) { 116 | real_revoke_object_URL.call(real_URL, object_URL); 117 | } 118 | }; 119 | FBB_proto.append = function(data/*, endings*/) { 120 | var bb = this.data; 121 | // decode data to a binary string 122 | if (Uint8Array && (data instanceof ArrayBuffer || data instanceof Uint8Array)) { 123 | var 124 | str = "" 125 | , buf = new Uint8Array(data) 126 | , i = 0 127 | , buf_len = buf.length 128 | ; 129 | for (; i < buf_len; i++) { 130 | str += String.fromCharCode(buf[i]); 131 | } 132 | bb.push(str); 133 | } else if (get_class(data) === "Blob" || get_class(data) === "File") { 134 | if (FileReaderSync) { 135 | var fr = new FileReaderSync; 136 | bb.push(fr.readAsBinaryString(data)); 137 | } else { 138 | // async FileReader won't work as BlobBuilder is sync 139 | throw new FileException("NOT_READABLE_ERR"); 140 | } 141 | } else if (data instanceof FakeBlob) { 142 | if (data.encoding === "base64" && atob) { 143 | bb.push(atob(data.data)); 144 | } else if (data.encoding === "URI") { 145 | bb.push(decodeURIComponent(data.data)); 146 | } else if (data.encoding === "raw") { 147 | bb.push(data.data); 148 | } 149 | } else { 150 | if (typeof data !== "string") { 151 | data += ""; // convert unsupported types to strings 152 | } 153 | // decode UTF-16 to binary string 154 | bb.push(unescape(encodeURIComponent(data))); 155 | } 156 | }; 157 | FBB_proto.getBlob = function(type) { 158 | if (!arguments.length) { 159 | type = null; 160 | } 161 | return new FakeBlob(this.data.join(""), type, "raw"); 162 | }; 163 | FBB_proto.toString = function() { 164 | return "[object BlobBuilder]"; 165 | }; 166 | FB_proto.slice = function(start, end, type) { 167 | var args = arguments.length; 168 | if (args < 3) { 169 | type = null; 170 | } 171 | return new FakeBlob( 172 | this.data.slice(start, args > 1 ? end : this.data.length) 173 | , type 174 | , this.encoding 175 | ); 176 | }; 177 | FB_proto.toString = function() { 178 | return "[object Blob]"; 179 | }; 180 | FB_proto.close = function() { 181 | this.size = 0; 182 | delete this.data; 183 | }; 184 | return FakeBlobBuilder; 185 | }(view)); 186 | 187 | view.Blob = function(blobParts, options) { 188 | var type = options ? (options.type || "") : ""; 189 | var builder = new BlobBuilder(); 190 | if (blobParts) { 191 | for (var i = 0, len = blobParts.length; i < len; i++) { 192 | if (Uint8Array && blobParts[i] instanceof Uint8Array) { 193 | builder.append(blobParts[i].buffer); 194 | } 195 | else { 196 | builder.append(blobParts[i]); 197 | } 198 | } 199 | } 200 | var blob = builder.getBlob(type); 201 | if (!blob.slice && blob.webkitSlice) { 202 | blob.slice = blob.webkitSlice; 203 | } 204 | return blob; 205 | }; 206 | 207 | var getPrototypeOf = Object.getPrototypeOf || function(object) { 208 | return object.__proto__; 209 | }; 210 | view.Blob.prototype = getPrototypeOf(new view.Blob()); 211 | }(typeof self !== "undefined" && self || typeof window !== "undefined" && window || this.content || this)); 212 | --------------------------------------------------------------------------------