├── .editorconfig ├── .eslintrc.yaml ├── .gitignore ├── .jsdoc.json ├── .npmignore ├── .travis.yml ├── CHANGELOG.md ├── CODE_OF_CONDUCT.md ├── LICENSE ├── README.md ├── gulpfile.babel.js ├── lib ├── Gist.js ├── GitHub.js ├── Issue.js ├── Markdown.js ├── Organization.js ├── Project.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.js ├── gist.spec.js ├── helpers ├── callbacks.js ├── getTestRepoName.js ├── helperFunctions.js └── wait.js ├── issue.spec.js ├── markdown.spec.js ├── organization.spec.js ├── project.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 | .nyc_output/ 8 | /out/ 9 | .DS_Store 10 | npm-debug.log 11 | sauce.json 12 | -------------------------------------------------------------------------------- /.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 | lib/ 5 | .nyc_output/ 6 | .DS_Store 7 | sauce.json 8 | -------------------------------------------------------------------------------- /.travis.yml: -------------------------------------------------------------------------------- 1 | sudo: false 2 | language: node_js 3 | node_js: 4 | - '11' 5 | - '10' 6 | - '8' 7 | cache: 8 | directories: 9 | - node_modules 10 | before_install: npm install -g npm@latest 11 | before_script: 12 | - npm run lint 13 | script: 14 | - npm run test-coverage 15 | after_success: 16 | - npm run codecov 17 | before_deploy: 18 | - npm run build 19 | deploy: 20 | provider: npm 21 | skip_cleanup: true 22 | on: 23 | tags: true 24 | email: clayreimann@gmail.com 25 | api_key: 26 | secure: WnLh1m02aF7NvFNILCZ8KsjPuDeSddQI87y8dwAixStr2FhQyz8FIKZN2Qj1N1Q9ZJvBETe5HWs1c9yOjTKBkD0d/eU2hlpnB9WXEFRJVDjiUuMnpAMMvuqTZwYg6kXq5N+of95PX58AYiBiV/qwsdUr/MgjEEYLt5UZgRYQRvE= 27 | -------------------------------------------------------------------------------- /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 | -------------------------------------------------------------------------------- /CODE_OF_CONDUCT.md: -------------------------------------------------------------------------------- 1 | # Contributor Covenant Code of Conduct 2 | 3 | ## Our Pledge 4 | 5 | In the interest of fostering an open and welcoming environment, we as 6 | contributors and maintainers pledge to making participation in our project and 7 | our community a harassment-free experience for everyone, regardless of age, body 8 | size, disability, ethnicity, sex characteristics, gender identity and expression, 9 | level of experience, education, socio-economic status, nationality, personal 10 | appearance, race, religion, or sexual identity and orientation. 11 | 12 | ## Our Standards 13 | 14 | Examples of behavior that contributes to creating a positive environment 15 | include: 16 | 17 | * Using welcoming and inclusive language 18 | * Being respectful of differing viewpoints and experiences 19 | * Gracefully accepting constructive criticism 20 | * Focusing on what is best for the community 21 | * Showing empathy towards other community members 22 | 23 | Examples of unacceptable behavior by participants include: 24 | 25 | * The use of sexualized language or imagery and unwelcome sexual attention or 26 | advances 27 | * Trolling, insulting/derogatory comments, and personal or political attacks 28 | * Public or private harassment 29 | * Publishing others' private information, such as a physical or electronic 30 | address, without explicit permission 31 | * Other conduct which could reasonably be considered inappropriate in a 32 | professional setting 33 | 34 | ## Our Responsibilities 35 | 36 | Project maintainers are responsible for clarifying the standards of acceptable 37 | behavior and are expected to take appropriate and fair corrective action in 38 | response to any instances of unacceptable behavior. 39 | 40 | Project maintainers have the right and responsibility to remove, edit, or 41 | reject comments, commits, code, wiki edits, issues, and other contributions 42 | that are not aligned to this Code of Conduct, or to ban temporarily or 43 | permanently any contributor for other behaviors that they deem inappropriate, 44 | threatening, offensive, or harmful. 45 | 46 | ## Scope 47 | 48 | This Code of Conduct applies both within project spaces and in public spaces 49 | when an individual is representing the project or its community. Examples of 50 | representing a project or community include using an official project e-mail 51 | address, posting via an official social media account, or acting as an appointed 52 | representative at an online or offline event. Representation of a project may be 53 | further defined and clarified by project maintainers. 54 | 55 | ## Enforcement 56 | 57 | Instances of abusive, harassing, or otherwise unacceptable behavior may be 58 | reported by contacting the project team at . All 59 | complaints will be reviewed and investigated and will result in a response that 60 | is deemed necessary and appropriate to the circumstances. The project team is 61 | obligated to maintain confidentiality with regard to the reporter of an incident. 62 | Further details of specific enforcement policies may be posted separately. 63 | 64 | Project maintainers who do not follow or enforce the Code of Conduct in good 65 | faith may face temporary or permanent repercussions as determined by other 66 | members of the project's leadership. 67 | 68 | ## Attribution 69 | 70 | This Code of Conduct is adapted from the [Contributor Covenant][homepage], version 1.4, 71 | available at https://www.contributor-covenant.org/version/1/4/code-of-conduct.html 72 | 73 | [homepage]: https://www.contributor-covenant.org 74 | 75 | For answers to common questions about this code of conduct, see 76 | https://www.contributor-covenant.org/faq 77 | -------------------------------------------------------------------------------- /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 | # Maintainers wanted 2 | [Apply within](https://github.com/github-tools/github/issues/539) 3 | 4 | # Github.js 5 | 6 | [![Downloads per month](https://img.shields.io/npm/dm/github-api.svg?maxAge=2592000)][npm-package] 7 | [![Latest version](https://img.shields.io/npm/v/github-api.svg?maxAge=3600)][npm-package] 8 | [![Gitter](https://img.shields.io/gitter/room/github-tools/github.js.svg?maxAge=2592000)][gitter] 9 | [![Travis](https://img.shields.io/travis/github-tools/github.svg?maxAge=60)][travis-ci] 10 | [![Codecov](https://img.shields.io/codecov/c/github/github-tools/github.svg?maxAge=2592000)][codecov] 11 | 12 | `Github.js` provides a minimal higher-level wrapper around Github's API. 13 | 14 | ## Usage 15 | 16 | ```javascript 17 | /* 18 | Data can be retrieved from the API either using callbacks (as in versions < 1.0) 19 | or using a new promise-based API. The promise-based API returns the raw Axios 20 | request promise. 21 | */ 22 | import GitHub from 'github-api'; 23 | 24 | // unauthenticated client 25 | const gh = new GitHub(); 26 | let gist = gh.getGist(); // not a gist yet 27 | gist.create({ 28 | public: true, 29 | description: 'My first gist', 30 | files: { 31 | "file1.txt": { 32 | content: "Aren't gists great!" 33 | } 34 | } 35 | }).then(function({data}) { 36 | // Promises! 37 | let createdGist = data; 38 | return gist.read(); 39 | }).then(function({data}) { 40 | let retrievedGist = data; 41 | // do interesting things 42 | }); 43 | ``` 44 | 45 | ```javascript 46 | var GitHub = require('github-api'); 47 | 48 | // basic auth 49 | var gh = new GitHub({ 50 | username: 'FOO', 51 | password: 'NotFoo' 52 | /* also acceptable: 53 | token: 'MY_OAUTH_TOKEN' 54 | */ 55 | }); 56 | 57 | var me = gh.getUser(); // no user specified defaults to the user for whom credentials were provided 58 | me.listNotifications(function(err, notifications) { 59 | // do some stuff 60 | }); 61 | 62 | var clayreimann = gh.getUser('clayreimann'); 63 | clayreimann.listStarredRepos(function(err, repos) { 64 | // look at all the starred repos! 65 | }); 66 | ``` 67 | 68 | ## API Documentation 69 | 70 | [API documentation][docs] is hosted on github pages, and is generated from JSDoc; any contributions 71 | should include updated JSDoc. 72 | 73 | ## Installation 74 | `Github.js` is available from `npm` or [unpkg][unpkg]. 75 | 76 | ```shell 77 | npm install github-api 78 | ``` 79 | 80 | ```html 81 | 82 | 83 | 84 | 85 | 86 | ``` 87 | 88 | ## Compatibility 89 | `Github.js` is tested on node's LTS and current versions. 90 | 91 | [codecov]: https://codecov.io/github/github-tools/github?branch=master 92 | [docs]: http://github-tools.github.io/github/ 93 | [gitter]: https://gitter.im/github-tools/github 94 | [npm-package]: https://www.npmjs.com/package/github-api/ 95 | [unpkg]: https://unpkg.com/github-api/ 96 | [travis-ci]: https://travis-ci.org/github-tools/github 97 | 98 | ## Contributing 99 | 100 | We welcome contributions of all types! This section will guide you through setting up your development environment. 101 | 102 | ### Setup 103 | 104 | 1. [Install Node](https://nodejs.org/en/) version 8,10 or 11. It can often help to use a Node version switcher such as [NVM](https://github.com/nvm-sh/nvm). 105 | 2. Fork this repo to your GitHub account. 106 | 3. Clone the fork to your development machine (`git clone https://github.com/{YOUR_USERNAME}/github`). 107 | 4. From the root of the cloned repo, run `npm install`. 108 | 5. Email jaredrewerts@gmail.com with the subject **GitHub API - Personal Access Token Request** 109 | 110 | A personal access token for our test user, @github-tools-test, will be generated for you. 111 | 112 | 6. Set the environment variable `GHTOOLS_USER` to `github-tools-test`. 113 | 114 | `export GHTOOLS_USER=github-tools-test` 115 | 116 | 7. Set the environment variable `GHTOOLS_PASSWORD` to the personal access token that was generated for you. 117 | 118 | `export GHTOOLS_PASSWORD={YOUR_PAT}` 119 | 120 | **NOTE** Windows users can use [this guide](http://www.dowdandassociates.com/blog/content/howto-set-an-environment-variable-in-windows-command-line-and-registry/) to learn about setting environment variables on Windows. 121 | 122 | ### Tests 123 | 124 | The main way we write code for `github-api` is using test-driven development. We use Mocha to run our tests. Given that the bulk of this library is just interacting with GitHub's API, nearly all of our tests are integration tests. 125 | 126 | To run the test suite, run `npm run test`. 127 | -------------------------------------------------------------------------------- /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 commits 113 | * @see https://developer.github.com/v3/gists/#list-gist-commits 114 | * @param {Requestable.callback} [cb] - will receive the array of commits 115 | * @return {Promise} - the Promise for the http request 116 | */ 117 | listCommits(cb) { 118 | return this._requestAllPages(`/gists/${this.__id}/commits`, null, cb); 119 | } 120 | 121 | /** 122 | * Fetch one of the gist's revision. 123 | * @see https://developer.github.com/v3/gists/#get-a-specific-revision-of-a-gist 124 | * @param {string} revision - the id of the revision 125 | * @param {Requestable.callback} [cb] - will receive the revision 126 | * @return {Promise} - the Promise for the http request 127 | */ 128 | getRevision(revision, cb) { 129 | return this._request('GET', `/gists/${this.__id}/${revision}`, null, cb); 130 | } 131 | 132 | /** 133 | * List the gist's comments 134 | * @see https://developer.github.com/v3/gists/comments/#list-comments-on-a-gist 135 | * @param {Requestable.callback} [cb] - will receive the array of comments 136 | * @return {Promise} - the promise for the http request 137 | */ 138 | listComments(cb) { 139 | return this._requestAllPages(`/gists/${this.__id}/comments`, null, cb); 140 | } 141 | 142 | /** 143 | * Fetch one of the gist's comments 144 | * @see https://developer.github.com/v3/gists/comments/#get-a-single-comment 145 | * @param {number} comment - the id of the comment 146 | * @param {Requestable.callback} [cb] - will receive the comment 147 | * @return {Promise} - the Promise for the http request 148 | */ 149 | getComment(comment, cb) { 150 | return this._request('GET', `/gists/${this.__id}/comments/${comment}`, null, cb); 151 | } 152 | 153 | /** 154 | * Comment on a gist 155 | * @see https://developer.github.com/v3/gists/comments/#create-a-comment 156 | * @param {string} comment - the comment to add 157 | * @param {Requestable.callback} [cb] - the function that receives the API result 158 | * @return {Promise} - the Promise for the http request 159 | */ 160 | createComment(comment, cb) { 161 | return this._request('POST', `/gists/${this.__id}/comments`, {body: comment}, cb); 162 | } 163 | 164 | /** 165 | * Edit a comment on the gist 166 | * @see https://developer.github.com/v3/gists/comments/#edit-a-comment 167 | * @param {number} comment - the id of the comment 168 | * @param {string} body - the new comment 169 | * @param {Requestable.callback} [cb] - will receive the modified comment 170 | * @return {Promise} - the promise for the http request 171 | */ 172 | editComment(comment, body, cb) { 173 | return this._request('PATCH', `/gists/${this.__id}/comments/${comment}`, {body: body}, cb); 174 | } 175 | 176 | /** 177 | * Delete a comment on the gist. 178 | * @see https://developer.github.com/v3/gists/comments/#delete-a-comment 179 | * @param {number} comment - the id of the comment 180 | * @param {Requestable.callback} [cb] - will receive true if the request succeeds 181 | * @return {Promise} - the Promise for the http request 182 | */ 183 | deleteComment(comment, cb) { 184 | return this._request('DELETE', `/gists/${this.__id}/comments/${comment}`, null, cb); 185 | } 186 | } 187 | 188 | module.exports = Gist; 189 | -------------------------------------------------------------------------------- /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 | import Project from './Project'; 19 | 20 | /** 21 | * GitHub encapsulates the functionality to create various API wrapper objects. 22 | */ 23 | class GitHub { 24 | /** 25 | * Create a new GitHub. 26 | * @param {Requestable.auth} [auth] - the credentials to authenticate to Github. If auth is 27 | * not provided requests will be made unauthenticated 28 | * @param {string} [apiBase=https://api.github.com] - the base Github API URL 29 | */ 30 | constructor(auth, apiBase = 'https://api.github.com') { 31 | this.__apiBase = apiBase; 32 | this.__auth = auth || {}; 33 | } 34 | 35 | /** 36 | * Create a new Gist wrapper 37 | * @param {string} [id] - the id for the gist, leave undefined when creating a new gist 38 | * @return {Gist} 39 | */ 40 | getGist(id) { 41 | return new Gist(id, this.__auth, this.__apiBase); 42 | } 43 | 44 | /** 45 | * Create a new User wrapper 46 | * @param {string} [user] - the name of the user to get information about 47 | * leave undefined for the authenticated user 48 | * @return {User} 49 | */ 50 | getUser(user) { 51 | return new User(user, this.__auth, this.__apiBase); 52 | } 53 | 54 | /** 55 | * Create a new Organization wrapper 56 | * @param {string} organization - the name of the organization 57 | * @return {Organization} 58 | */ 59 | getOrganization(organization) { 60 | return new Organization(organization, this.__auth, this.__apiBase); 61 | } 62 | 63 | /** 64 | * create a new Team wrapper 65 | * @param {string} teamId - the name of the team 66 | * @return {team} 67 | */ 68 | getTeam(teamId) { 69 | return new Team(teamId, this.__auth, this.__apiBase); 70 | } 71 | 72 | /** 73 | * Create a new Repository wrapper 74 | * @param {string} user - the user who owns the repository 75 | * @param {string} repo - the name of the repository 76 | * @return {Repository} 77 | */ 78 | getRepo(user, repo) { 79 | return new Repository(this._getFullName(user, repo), this.__auth, this.__apiBase); 80 | } 81 | 82 | /** 83 | * Create a new Issue wrapper 84 | * @param {string} user - the user who owns the repository 85 | * @param {string} repo - the name of the repository 86 | * @return {Issue} 87 | */ 88 | getIssues(user, repo) { 89 | return new Issue(this._getFullName(user, repo), this.__auth, this.__apiBase); 90 | } 91 | 92 | /** 93 | * Create a new Search wrapper 94 | * @param {string} query - the query to search for 95 | * @return {Search} 96 | */ 97 | search(query) { 98 | return new Search(query, this.__auth, this.__apiBase); 99 | } 100 | 101 | /** 102 | * Create a new RateLimit wrapper 103 | * @return {RateLimit} 104 | */ 105 | getRateLimit() { 106 | return new RateLimit(this.__auth, this.__apiBase); 107 | } 108 | 109 | /** 110 | * Create a new Markdown wrapper 111 | * @return {Markdown} 112 | */ 113 | getMarkdown() { 114 | return new Markdown(this.__auth, this.__apiBase); 115 | } 116 | 117 | /** 118 | * Create a new Project wrapper 119 | * @param {string} id - the id of the project 120 | * @return {Project} 121 | */ 122 | getProject(id) { 123 | return new Project(id, this.__auth, this.__apiBase); 124 | } 125 | 126 | /** 127 | * Computes the full repository name 128 | * @param {string} user - the username (or the full name) 129 | * @param {string} repo - the repository name, must not be passed if `user` is the full name 130 | * @return {string} the repository's full name 131 | */ 132 | _getFullName(user, repo) { 133 | let fullname = user; 134 | 135 | if (repo) { 136 | fullname = `${user}/${repo}`; 137 | } 138 | 139 | return fullname; 140 | } 141 | } 142 | 143 | module.exports = GitHub; 144 | -------------------------------------------------------------------------------- /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 milestone 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 milestone 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 updated milestone 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 status 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 | * List the labels for the repository 207 | * @see https://developer.github.com/v3/issues/labels/#list-all-labels-for-this-repository 208 | * @param {Object} options - filtering options 209 | * @param {Requestable.callback} [cb] - will receive the array of labels 210 | * @return {Promise} - the promise for the http request 211 | */ 212 | listLabels(options, cb) { 213 | return this._request('GET', `/repos/${this.__repository}/labels`, options, cb); 214 | } 215 | 216 | /** 217 | * Get a label 218 | * @see https://developer.github.com/v3/issues/labels/#get-a-single-label 219 | * @param {string} label - the name of the label to fetch 220 | * @param {Requestable.callback} [cb] - will receive the label 221 | * @return {Promise} - the promise for the http request 222 | */ 223 | getLabel(label, cb) { 224 | return this._request('GET', `/repos/${this.__repository}/labels/${label}`, null, cb); 225 | } 226 | 227 | /** 228 | * Edit a label 229 | * @see https://developer.github.com/v3/issues/labels/#update-a-label 230 | * @param {string} label - the name of the label to edit 231 | * @param {Object} labelData - the updates to make to the label 232 | * @param {Requestable.callback} [cb] - will receive the updated label 233 | * @return {Promise} - the promise for the http request 234 | */ 235 | editLabel(label, labelData, cb) { 236 | return this._request('PATCH', `/repos/${this.__repository}/labels/${label}`, labelData, cb); 237 | } 238 | 239 | /** 240 | * Delete a label 241 | * @see https://developer.github.com/v3/issues/labels/#delete-a-label 242 | * @param {string} label - the name of the label to delete 243 | * @param {Requestable.callback} [cb] - will receive the status 244 | * @return {Promise} - the promise for the http request 245 | */ 246 | deleteLabel(label, cb) { 247 | return this._request('DELETE', `/repos/${this.__repository}/labels/${label}`, null, cb); 248 | } 249 | } 250 | 251 | module.exports = Issue; 252 | -------------------------------------------------------------------------------- /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 | * Renders html from Markdown text 12 | */ 13 | class Markdown extends Requestable { 14 | /** 15 | * construct a Markdown 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, true); 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 | * Get information about all projects 99 | * @see https://developer.github.com/v3/projects/#list-organization-projects 100 | * @param {Requestable.callback} [cb] - will receive the list of projects 101 | * @return {Promise} - the promise for the http request 102 | */ 103 | listProjects(cb) { 104 | return this._requestAllPages(`/orgs/${this.__name}/projects`, {AcceptHeader: 'inertia-preview'}, cb); 105 | } 106 | 107 | /** 108 | * Create a new project 109 | * @see https://developer.github.com/v3/repos/projects/#create-a-project 110 | * @param {Object} options - the description of the project 111 | * @param {Requestable.callback} cb - will receive the newly created project 112 | * @return {Promise} - the promise for the http request 113 | */ 114 | createProject(options, cb) { 115 | options = options || {}; 116 | options.AcceptHeader = 'inertia-preview'; 117 | return this._request('POST', `/orgs/${this.__name}/projects`, options, cb); 118 | } 119 | } 120 | 121 | module.exports = Organization; 122 | -------------------------------------------------------------------------------- /lib/Project.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 | * Project encapsulates the functionality to create, query, and modify cards and columns. 12 | */ 13 | class Project extends Requestable { 14 | /** 15 | * Create a Project. 16 | * @param {string} id - the id of the project 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, 'inertia-preview'); 22 | this.__id = id; 23 | } 24 | 25 | /** 26 | * Get information about a project 27 | * @see https://developer.github.com/v3/projects/#get-a-project 28 | * @param {Requestable.callback} cb - will receive the project information 29 | * @return {Promise} - the promise for the http request 30 | */ 31 | getProject(cb) { 32 | return this._request('GET', `/projects/${this.__id}`, null, cb); 33 | } 34 | 35 | /** 36 | * Edit a project 37 | * @see https://developer.github.com/v3/projects/#update-a-project 38 | * @param {Object} options - the description of the project 39 | * @param {Requestable.callback} cb - will receive the modified project 40 | * @return {Promise} - the promise for the http request 41 | */ 42 | updateProject(options, cb) { 43 | return this._request('PATCH', `/projects/${this.__id}`, options, cb); 44 | } 45 | 46 | /** 47 | * Delete a project 48 | * @see https://developer.github.com/v3/projects/#delete-a-project 49 | * @param {Requestable.callback} cb - will receive true if the operation is successful 50 | * @return {Promise} - the promise for the http request 51 | */ 52 | deleteProject(cb) { 53 | return this._request('DELETE', `/projects/${this.__id}`, null, cb); 54 | } 55 | 56 | /** 57 | * Get information about all columns of a project 58 | * @see https://developer.github.com/v3/projects/columns/#list-project-columns 59 | * @param {Requestable.callback} [cb] - will receive the list of columns 60 | * @return {Promise} - the promise for the http request 61 | */ 62 | listProjectColumns(cb) { 63 | return this._requestAllPages(`/projects/${this.__id}/columns`, null, cb); 64 | } 65 | 66 | /** 67 | * Get information about a column 68 | * @see https://developer.github.com/v3/projects/columns/#get-a-project-column 69 | * @param {string} colId - the id of the column 70 | * @param {Requestable.callback} cb - will receive the column information 71 | * @return {Promise} - the promise for the http request 72 | */ 73 | getProjectColumn(colId, cb) { 74 | return this._request('GET', `/projects/columns/${colId}`, null, cb); 75 | } 76 | 77 | /** 78 | * Create a new column 79 | * @see https://developer.github.com/v3/projects/columns/#create-a-project-column 80 | * @param {Object} options - the description of the column 81 | * @param {Requestable.callback} cb - will receive the newly created column 82 | * @return {Promise} - the promise for the http request 83 | */ 84 | createProjectColumn(options, cb) { 85 | return this._request('POST', `/projects/${this.__id}/columns`, options, cb); 86 | } 87 | 88 | /** 89 | * Edit a column 90 | * @see https://developer.github.com/v3/projects/columns/#update-a-project-column 91 | * @param {string} colId - the column id 92 | * @param {Object} options - the description of the column 93 | * @param {Requestable.callback} cb - will receive the modified column 94 | * @return {Promise} - the promise for the http request 95 | */ 96 | updateProjectColumn(colId, options, cb) { 97 | return this._request('PATCH', `/projects/columns/${colId}`, options, cb); 98 | } 99 | 100 | /** 101 | * Delete a column 102 | * @see https://developer.github.com/v3/projects/columns/#delete-a-project-column 103 | * @param {string} colId - the column to be deleted 104 | * @param {Requestable.callback} cb - will receive true if the operation is successful 105 | * @return {Promise} - the promise for the http request 106 | */ 107 | deleteProjectColumn(colId, cb) { 108 | return this._request('DELETE', `/projects/columns/${colId}`, null, cb); 109 | } 110 | 111 | /** 112 | * Move a column 113 | * @see https://developer.github.com/v3/projects/columns/#move-a-project-column 114 | * @param {string} colId - the column to be moved 115 | * @param {string} position - can be one of first, last, or after:, 116 | * where is the id value of a column in the same project. 117 | * @param {Requestable.callback} cb - will receive true if the operation is successful 118 | * @return {Promise} - the promise for the http request 119 | */ 120 | moveProjectColumn(colId, position, cb) { 121 | return this._request( 122 | 'POST', 123 | `/projects/columns/${colId}/moves`, 124 | {position: position}, 125 | cb 126 | ); 127 | } 128 | 129 | /** 130 | * Get information about all cards of a project 131 | * @see https://developer.github.com/v3/projects/cards/#list-project-cards 132 | * @param {Requestable.callback} [cb] - will receive the list of cards 133 | * @return {Promise} - the promise for the http request 134 | */ 135 | listProjectCards(cb) { 136 | return this.listProjectColumns() 137 | .then(({data}) => { 138 | return Promise.all(data.map((column) => { 139 | return this._requestAllPages(`/projects/columns/${column.id}/cards`, null); 140 | })); 141 | }).then((cardsInColumns) => { 142 | const cards = cardsInColumns.reduce((prev, {data}) => { 143 | prev.push(...data); 144 | return prev; 145 | }, []); 146 | if (cb) { 147 | cb(null, cards); 148 | } 149 | return cards; 150 | }).catch((err) => { 151 | if (cb) { 152 | cb(err); 153 | return; 154 | } 155 | throw err; 156 | }); 157 | } 158 | 159 | /** 160 | * Get information about all cards of a column 161 | * @see https://developer.github.com/v3/projects/cards/#list-project-cards 162 | * @param {string} colId - the id of the column 163 | * @param {Requestable.callback} [cb] - will receive the list of cards 164 | * @return {Promise} - the promise for the http request 165 | */ 166 | listColumnCards(colId, cb) { 167 | return this._requestAllPages(`/projects/columns/${colId}/cards`, null, cb); 168 | } 169 | 170 | /** 171 | * Get information about a card 172 | * @see https://developer.github.com/v3/projects/cards/#get-a-project-card 173 | * @param {string} cardId - the id of the card 174 | * @param {Requestable.callback} cb - will receive the card information 175 | * @return {Promise} - the promise for the http request 176 | */ 177 | getProjectCard(cardId, cb) { 178 | return this._request('GET', `/projects/columns/cards/${cardId}`, null, cb); 179 | } 180 | 181 | /** 182 | * Create a new card 183 | * @see https://developer.github.com/v3/projects/cards/#create-a-project-card 184 | * @param {string} colId - the column id 185 | * @param {Object} options - the description of the card 186 | * @param {Requestable.callback} cb - will receive the newly created card 187 | * @return {Promise} - the promise for the http request 188 | */ 189 | createProjectCard(colId, options, cb) { 190 | return this._request('POST', `/projects/columns/${colId}/cards`, options, cb); 191 | } 192 | 193 | /** 194 | * Edit a card 195 | * @see https://developer.github.com/v3/projects/cards/#update-a-project-card 196 | * @param {string} cardId - the card id 197 | * @param {Object} options - the description of the card 198 | * @param {Requestable.callback} cb - will receive the modified card 199 | * @return {Promise} - the promise for the http request 200 | */ 201 | updateProjectCard(cardId, options, cb) { 202 | return this._request('PATCH', `/projects/columns/cards/${cardId}`, options, cb); 203 | } 204 | 205 | /** 206 | * Delete a card 207 | * @see https://developer.github.com/v3/projects/cards/#delete-a-project-card 208 | * @param {string} cardId - the card to be deleted 209 | * @param {Requestable.callback} cb - will receive true if the operation is successful 210 | * @return {Promise} - the promise for the http request 211 | */ 212 | deleteProjectCard(cardId, cb) { 213 | return this._request('DELETE', `/projects/columns/cards/${cardId}`, null, cb); 214 | } 215 | 216 | /** 217 | * Move a card 218 | * @see https://developer.github.com/v3/projects/cards/#move-a-project-card 219 | * @param {string} cardId - the card to be moved 220 | * @param {string} position - can be one of top, bottom, or after:, 221 | * where is the id value of a card in the same project. 222 | * @param {string} colId - the id value of a column in the same project. 223 | * @param {Requestable.callback} cb - will receive true if the operation is successful 224 | * @return {Promise} - the promise for the http request 225 | */ 226 | moveProjectCard(cardId, position, colId, cb) { 227 | return this._request( 228 | 'POST', 229 | `/projects/columns/cards/${cardId}/moves`, 230 | {position: position, column_id: colId}, // eslint-disable-line camelcase 231 | cb 232 | ); 233 | } 234 | } 235 | 236 | module.exports = Project; 237 | -------------------------------------------------------------------------------- /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/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 | 12 | const log = debug('github:request'); 13 | 14 | /** 15 | * The error structure returned when a network call fails 16 | */ 17 | class ResponseError extends Error { 18 | /** 19 | * Construct a new ResponseError 20 | * @param {string} message - an message to return instead of the the default error message 21 | * @param {string} path - the requested path 22 | * @param {Object} response - the object returned by Axios 23 | */ 24 | constructor(message, path, response) { 25 | super(message); 26 | this.path = path; 27 | this.request = response.config; 28 | this.response = (response || {}).response || response; 29 | this.status = response.status; 30 | } 31 | } 32 | 33 | /** 34 | * Requestable wraps the logic for making http requests to the API 35 | */ 36 | class Requestable { 37 | /** 38 | * Either a username and password or an oauth token for Github 39 | * @typedef {Object} Requestable.auth 40 | * @prop {string} [username] - the Github username 41 | * @prop {string} [password] - the user's password 42 | * @prop {token} [token] - an OAuth token 43 | */ 44 | /** 45 | * Initialize the http internals. 46 | * @param {Requestable.auth} [auth] - the credentials to authenticate to Github. If auth is 47 | * not provided request will be made unauthenticated 48 | * @param {string} [apiBase=https://api.github.com] - the base Github API URL 49 | * @param {string} [AcceptHeader=v3] - the accept header for the requests 50 | */ 51 | constructor(auth, apiBase, AcceptHeader) { 52 | this.__apiBase = apiBase || 'https://api.github.com'; 53 | this.__auth = { 54 | token: auth.token, 55 | username: auth.username, 56 | password: auth.password, 57 | }; 58 | this.__AcceptHeader = AcceptHeader || 'v3'; 59 | 60 | if (auth.token) { 61 | this.__authorizationHeader = 'token ' + auth.token; 62 | } else if (auth.username && auth.password) { 63 | this.__authorizationHeader = 'Basic ' + Base64.encode(auth.username + ':' + auth.password); 64 | } 65 | } 66 | 67 | /** 68 | * Compute the URL to use to make a request. 69 | * @private 70 | * @param {string} path - either a URL relative to the API base or an absolute URL 71 | * @return {string} - the URL to use 72 | */ 73 | __getURL(path) { 74 | let url = path; 75 | 76 | if (path.indexOf('//') === -1) { 77 | url = this.__apiBase + path; 78 | } 79 | 80 | let newCacheBuster = 'timestamp=' + new Date().getTime(); 81 | return url.replace(/(timestamp=\d+)/, newCacheBuster); 82 | } 83 | 84 | /** 85 | * Compute the headers required for an API request. 86 | * @private 87 | * @param {boolean} raw - if the request should be treated as JSON or as a raw request 88 | * @param {string} AcceptHeader - the accept header for the request 89 | * @return {Object} - the headers to use in the request 90 | */ 91 | __getRequestHeaders(raw, AcceptHeader) { 92 | let headers = { 93 | 'Content-Type': 'application/json;charset=UTF-8', 94 | 'Accept': 'application/vnd.github.' + (AcceptHeader || this.__AcceptHeader), 95 | }; 96 | 97 | if (raw) { 98 | headers.Accept += '.raw'; 99 | } 100 | headers.Accept += '+json'; 101 | 102 | if (this.__authorizationHeader) { 103 | headers.Authorization = this.__authorizationHeader; 104 | } 105 | 106 | return headers; 107 | } 108 | 109 | /** 110 | * Sets the default options for API requests 111 | * @protected 112 | * @param {Object} [requestOptions={}] - the current options for the request 113 | * @return {Object} - the options to pass to the request 114 | */ 115 | _getOptionsWithDefaults(requestOptions = {}) { 116 | if (!(requestOptions.visibility || requestOptions.affiliation)) { 117 | requestOptions.type = requestOptions.type || 'all'; 118 | } 119 | requestOptions.sort = requestOptions.sort || 'updated'; 120 | requestOptions.per_page = requestOptions.per_page || '100'; // eslint-disable-line 121 | 122 | return requestOptions; 123 | } 124 | 125 | /** 126 | * if a `Date` is passed to this function it will be converted to an ISO string 127 | * @param {*} date - the object to attempt to coerce into an ISO date string 128 | * @return {string} - the ISO representation of `date` or whatever was passed in if it was not a date 129 | */ 130 | _dateToISO(date) { 131 | if (date && (date instanceof Date)) { 132 | date = date.toISOString(); 133 | } 134 | 135 | return date; 136 | } 137 | 138 | /** 139 | * A function that receives the result of the API request. 140 | * @callback Requestable.callback 141 | * @param {Requestable.Error} error - the error returned by the API or `null` 142 | * @param {(Object|true)} result - the data returned by the API or `true` if the API returns `204 No Content` 143 | * @param {Object} request - the raw {@linkcode https://github.com/mzabriskie/axios#response-schema Response} 144 | */ 145 | /** 146 | * Make a request. 147 | * @param {string} method - the method for the request (GET, PUT, POST, DELETE) 148 | * @param {string} path - the path for the request 149 | * @param {*} [data] - the data to send to the server. For HTTP methods that don't have a body the data 150 | * will be sent as query parameters 151 | * @param {Requestable.callback} [cb] - the callback for the request 152 | * @param {boolean} [raw=false] - if the request should be sent as raw. If this is a falsy value then the 153 | * request will be made as JSON 154 | * @return {Promise} - the Promise for the http request 155 | */ 156 | _request(method, path, data, cb, raw) { 157 | const url = this.__getURL(path); 158 | 159 | const AcceptHeader = (data || {}).AcceptHeader; 160 | if (AcceptHeader) { 161 | delete data.AcceptHeader; 162 | } 163 | const headers = this.__getRequestHeaders(raw, AcceptHeader); 164 | 165 | let queryParams = {}; 166 | 167 | const shouldUseDataAsParams = data && (typeof data === 'object') && methodHasNoBody(method); 168 | if (shouldUseDataAsParams) { 169 | queryParams = data; 170 | data = undefined; 171 | } 172 | 173 | const config = { 174 | url: url, 175 | method: method, 176 | headers: headers, 177 | params: queryParams, 178 | data: data, 179 | responseType: raw ? 'text' : 'json', 180 | }; 181 | 182 | log(`${config.method} to ${config.url}`); 183 | const requestPromise = axios(config).catch(callbackErrorOrThrow(cb, path)); 184 | 185 | if (cb) { 186 | requestPromise.then((response) => { 187 | if (response.data && Object.keys(response.data).length > 0) { 188 | // When data has results 189 | cb(null, response.data, response); 190 | } else if (config.method !== 'GET' && Object.keys(response.data).length < 1) { 191 | // True when successful submit a request and receive a empty object 192 | cb(null, (response.status < 300), response); 193 | } else { 194 | cb(null, response.data, response); 195 | } 196 | }); 197 | } 198 | 199 | return requestPromise; 200 | } 201 | 202 | /** 203 | * Make a request to an endpoint the returns 204 when true and 404 when false 204 | * @param {string} path - the path to request 205 | * @param {Object} data - any query parameters for the request 206 | * @param {Requestable.callback} cb - the callback that will receive `true` or `false` 207 | * @param {method} [method=GET] - HTTP Method to use 208 | * @return {Promise} - the promise for the http request 209 | */ 210 | _request204or404(path, data, cb, method = 'GET') { 211 | return this._request(method, path, data) 212 | .then(function success(response) { 213 | if (cb) { 214 | cb(null, true, response); 215 | } 216 | return true; 217 | }, function failure(response) { 218 | if (response.response.status === 404) { 219 | if (cb) { 220 | cb(null, false, response); 221 | } 222 | return false; 223 | } 224 | 225 | if (cb) { 226 | cb(response); 227 | } 228 | throw response; 229 | }); 230 | } 231 | 232 | /** 233 | * Make a request and fetch all the available data. Github will paginate responses so for queries 234 | * that might span multiple pages this method is preferred to {@link Requestable#request} 235 | * @param {string} path - the path to request 236 | * @param {Object} options - the query parameters to include 237 | * @param {Requestable.callback} [cb] - the function to receive the data. The returned data will always be an array. 238 | * @param {Object[]} results - the partial results. This argument is intended for internal use only. 239 | * @return {Promise} - a promise which will resolve when all pages have been fetched 240 | * @deprecated This will be folded into {@link Requestable#_request} in the 2.0 release. 241 | */ 242 | _requestAllPages(path, options, cb, results) { 243 | results = results || []; 244 | 245 | return this._request('GET', path, options) 246 | .then((response) => { 247 | let thisGroup; 248 | if (response.data instanceof Array) { 249 | thisGroup = response.data; 250 | } else if (response.data.items instanceof Array) { 251 | thisGroup = response.data.items; 252 | } else { 253 | let message = `cannot figure out how to append ${response.data} to the result set`; 254 | throw new ResponseError(message, path, response); 255 | } 256 | results.push(...thisGroup); 257 | 258 | const nextUrl = getNextPage(response.headers.link); 259 | if(nextUrl) { 260 | if (!options) { 261 | options = {}; 262 | } 263 | options.page = parseInt( 264 | nextUrl.match(/([&\?]page=[0-9]*)/g) 265 | .shift() 266 | .split('=') 267 | .pop() 268 | ); 269 | if (!(options && typeof options.page !== 'number')) { 270 | log(`getting next page: ${nextUrl}`); 271 | return this._requestAllPages(nextUrl, options, cb, results); 272 | } 273 | } 274 | 275 | if (cb) { 276 | cb(null, results, response); 277 | } 278 | 279 | response.data = results; 280 | return response; 281 | }).catch(callbackErrorOrThrow(cb, path)); 282 | } 283 | } 284 | 285 | module.exports = Requestable; 286 | 287 | // ////////////////////////// // 288 | // Private helper functions // 289 | // ////////////////////////// // 290 | const METHODS_WITH_NO_BODY = ['GET', 'HEAD', 'DELETE']; 291 | function methodHasNoBody(method) { 292 | return METHODS_WITH_NO_BODY.indexOf(method) !== -1; 293 | } 294 | 295 | function getNextPage(linksHeader = '') { 296 | const links = linksHeader.split(/\s*,\s*/); // splits and strips the urls 297 | return links.reduce(function(nextUrl, link) { 298 | if (link.search(/rel="next"/) !== -1) { 299 | return (link.match(/<(.*)>/) || [])[1]; 300 | } 301 | 302 | return nextUrl; 303 | }, undefined); 304 | } 305 | 306 | function callbackErrorOrThrow(cb, path) { 307 | return function handler(object) { 308 | let error; 309 | if (object.hasOwnProperty('config')) { 310 | const {response: {status, statusText}, config: {method, url}} = object; 311 | let message = (`${status} error making request ${method} ${url}: "${statusText}"`); 312 | error = new ResponseError(message, path, object); 313 | log(`${message} ${JSON.stringify(object.data)}`); 314 | } else { 315 | error = object; 316 | } 317 | if (cb) { 318 | log('going to error callback'); 319 | cb(error); 320 | } else { 321 | log('throwing error'); 322 | throw error; 323 | } 324 | }; 325 | } 326 | -------------------------------------------------------------------------------- /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 followers of a user 86 | * @see https://developer.github.com/v3/users/followers/#list-followers-of-a-user 87 | * @param {Requestable.callback} [cb] - will receive the list of followers 88 | * @return {Promise} - the promise for the http request 89 | */ 90 | listFollowers(cb) { 91 | return this._request('GET', this.__getScopedUrl('followers'), null, cb); 92 | } 93 | 94 | /** 95 | * Lists the people who the authenticated user follows. 96 | * @see https://docs.github.com/en/rest/reference/users#list-the-people-the-authenticated-user-follows 97 | * @param {Requestable.callback} [cb] - will receive the list of who a user is following 98 | * @return {Promise} - the promise for the http request 99 | */ 100 | listFollowing(cb) { 101 | return this._request('GET', this.__getScopedUrl('following'), null, cb); 102 | } 103 | 104 | /** 105 | * List the user's gists 106 | * @see https://developer.github.com/v3/gists/#list-a-users-gists 107 | * @param {Requestable.callback} [cb] - will receive the list of gists 108 | * @return {Promise} - the promise for the http request 109 | */ 110 | listGists(cb) { 111 | return this._request('GET', this.__getScopedUrl('gists'), null, cb); 112 | } 113 | 114 | /** 115 | * List the user's notifications 116 | * @see https://developer.github.com/v3/activity/notifications/#list-your-notifications 117 | * @param {Object} [options={}] - any options to refine the search 118 | * @param {Requestable.callback} [cb] - will receive the list of repositories 119 | * @return {Promise} - the promise for the http request 120 | */ 121 | listNotifications(options, cb) { 122 | options = options || {}; 123 | if (typeof options === 'function') { 124 | cb = options; 125 | options = {}; 126 | } 127 | 128 | options.since = this._dateToISO(options.since); 129 | options.before = this._dateToISO(options.before); 130 | 131 | return this._request('GET', this.__getScopedUrl('notifications'), options, cb); 132 | } 133 | 134 | /** 135 | * Show the user's profile 136 | * @see https://developer.github.com/v3/users/#get-a-single-user 137 | * @param {Requestable.callback} [cb] - will receive the user's information 138 | * @return {Promise} - the promise for the http request 139 | */ 140 | getProfile(cb) { 141 | return this._request('GET', this.__getScopedUrl(''), null, cb); 142 | } 143 | 144 | /** 145 | * Gets the list of starred repositories for the user 146 | * @see https://developer.github.com/v3/activity/starring/#list-repositories-being-starred 147 | * @param {Requestable.callback} [cb] - will receive the list of starred repositories 148 | * @return {Promise} - the promise for the http request 149 | */ 150 | listStarredRepos(cb) { 151 | let requestOptions = this._getOptionsWithDefaults(); 152 | return this._requestAllPages(this.__getScopedUrl('starred'), requestOptions, cb); 153 | } 154 | 155 | /** 156 | * Gets the list of starred gists for the user 157 | * @see https://developer.github.com/v3/gists/#list-starred-gists 158 | * @param {Object} [options={}] - any options to refine the search 159 | * @param {Requestable.callback} [cb] - will receive the list of gists 160 | * @return {Promise} - the promise for the http request 161 | */ 162 | listStarredGists(options, cb) { 163 | options = options || {}; 164 | if (typeof options === 'function') { 165 | cb = options; 166 | options = {}; 167 | } 168 | options.since = this._dateToISO(options.since); 169 | return this._request('GET', '/gists/starred', options, cb); 170 | } 171 | 172 | /** 173 | * List email addresses for a user 174 | * @see https://developer.github.com/v3/users/emails/#list-email-addresses-for-a-user 175 | * @param {Requestable.callback} [cb] - will receive the list of emails 176 | * @return {Promise} - the promise for the http request 177 | */ 178 | getEmails(cb) { 179 | return this._request('GET', '/user/emails', null, cb); 180 | } 181 | 182 | /** 183 | * Have the authenticated user follow this user 184 | * @see https://developer.github.com/v3/users/followers/#follow-a-user 185 | * @param {string} username - the user to follow 186 | * @param {Requestable.callback} [cb] - will receive true if the request succeeds 187 | * @return {Promise} - the promise for the http request 188 | */ 189 | follow(username, cb) { 190 | return this._request('PUT', `/user/following/${username}`, null, cb); 191 | } 192 | 193 | /** 194 | * Have the currently authenticated user unfollow this user 195 | * @see https://developer.github.com/v3/users/followers/#follow-a-user 196 | * @param {string} username - the user to unfollow 197 | * @param {Requestable.callback} [cb] - receives true if the request succeeds 198 | * @return {Promise} - the promise for the http request 199 | */ 200 | unfollow(username, cb) { 201 | return this._request('DELETE', `/user/following/${username}`, null, cb); 202 | } 203 | 204 | /** 205 | * Create a new repository for the currently authenticated user 206 | * @see https://developer.github.com/v3/repos/#create 207 | * @param {object} options - the repository definition 208 | * @param {Requestable.callback} [cb] - will receive the API response 209 | * @return {Promise} - the promise for the http request 210 | */ 211 | createRepo(options, cb) { 212 | return this._request('POST', '/user/repos', options, cb); 213 | } 214 | } 215 | 216 | module.exports = User; 217 | -------------------------------------------------------------------------------- /mocha.opts: -------------------------------------------------------------------------------- 1 | --compilers js:babel-register 2 | --timeout 20000 3 | --slow 5000 4 | -------------------------------------------------------------------------------- /package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "github-api", 3 | "version": "3.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 | "Mathieu Dutour (https://github.com/mathieudutour)" 13 | ], 14 | "readmeFilename": "README.md", 15 | "scripts": { 16 | "clean": "gulp clean", 17 | "build": "gulp build", 18 | "test": "mocha --opts ./mocha.opts test/*.spec.js", 19 | "test-coverage": "NODE_ENV=test nyc mocha --opts ./mocha.opts test/*.spec.js", 20 | "test-verbose": "DEBUG=github* npm test", 21 | "lint": "gulp lint", 22 | "make-docs": "node_modules/.bin/jsdoc -c .jsdoc.json --verbose", 23 | "release": "./release.sh", 24 | "codecov": "nyc report --reporter=text-lcov > coverage.lcov && codecov" 25 | }, 26 | "babel": { 27 | "presets": [ 28 | "es2015" 29 | ], 30 | "plugins": [ 31 | [ 32 | "add-module-exports", 33 | "transform-es2015-modules-umd" 34 | ] 35 | ], 36 | "env": { 37 | "development": { 38 | "sourceMaps": "inline" 39 | }, 40 | "test": { 41 | "plugins": [ 42 | "istanbul" 43 | ] 44 | } 45 | } 46 | }, 47 | "nyc": { 48 | "sourceMap": false, 49 | "instrument": false 50 | }, 51 | "files": [ 52 | "dist/*" 53 | ], 54 | "dependencies": { 55 | "axios": "^0.21.1", 56 | "debug": "^2.2.0", 57 | "js-base64": "^2.1.9", 58 | "utf8": "^2.1.1" 59 | }, 60 | "devDependencies": { 61 | "babel-core": "^6.7.7", 62 | "babel-plugin-add-module-exports": "^0.2.1", 63 | "babel-plugin-istanbul": "3.0.0", 64 | "babel-plugin-transform-es2015-modules-umd": "^6.5.0", 65 | "babel-preset-es2015": "^6.5.0", 66 | "babel-register": "^6.7.2", 67 | "babelify": "^7.3.0", 68 | "browserify": "^13.0.0", 69 | "codecov": "^1.0.1", 70 | "del": "^2.2.0", 71 | "eslint-config-google": "^0.7.0", 72 | "eslint-plugin-mocha": "^4.7.0", 73 | "gulp": "^3.9.0", 74 | "gulp-babel": "^6.1.2", 75 | "gulp-eslint": "^3.0.1", 76 | "gulp-jscs": "^4.0.0", 77 | "gulp-jscs-stylish": "^1.3.0", 78 | "gulp-rename": "^1.2.2", 79 | "gulp-sourcemaps": "^2.2.0", 80 | "gulp-uglify": "^2.0.0", 81 | "jsdoc": "^3.4.0", 82 | "minami": "^1.1.1", 83 | "mocha": "^3.1.2", 84 | "must": "^0.13.1", 85 | "nock": "^9.0.2", 86 | "nyc": "9.0.1", 87 | "vinyl-buffer": "^1.0.0", 88 | "vinyl-source-stream": "^1.1.0" 89 | }, 90 | "repository": { 91 | "type": "git", 92 | "url": "git://github.com/github-tools/github.git" 93 | }, 94 | "keywords": [ 95 | "github", 96 | "api" 97 | ], 98 | "bugs": { 99 | "url": "https://github.com/github-tools/github/issues" 100 | } 101 | } 102 | -------------------------------------------------------------------------------- /release.sh: -------------------------------------------------------------------------------- 1 | #!/bin/bash 2 | # This is the automated release script 3 | 4 | # guard against stupid 5 | if [ -z "$1" ]; then 6 | echo "You must specify a new version level: [patch, minor, major]"; 7 | exit 1; 8 | fi 9 | 10 | # make sure all our dependencies are installed so we can publish docs 11 | npm install 12 | 13 | # try to build to make sure we don't publish something really broken 14 | npm run build 15 | 16 | # bump the version 17 | echo "npm version $1" 18 | npm version $1 19 | git push 20 | git push --tags 21 | 22 | # start from a clean state 23 | rm -rf docs/ out/ 24 | mkdir out 25 | 26 | # build the docs 27 | npm run make-docs 28 | VERSION=`ls docs/github-api` 29 | 30 | # switch to gh-pages and add the docs 31 | mv docs/github-api/* out/ 32 | rm -rf docs/ 33 | 34 | git checkout gh-pages 35 | mv out/* docs/ 36 | echo $VERSION >> _data/versions.csv 37 | git add . 38 | git commit -m "adding docs for v$VERSION" 39 | git push 40 | -------------------------------------------------------------------------------- /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.js'; 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.response.status).to.be.equal(401, 'Return 401 status for bad auth'); 87 | expect(err.response.data.message).to.equal('Requires authentication'); 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 | 44 | 48 | 49 | 50 | -------------------------------------------------------------------------------- /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.js'; 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-tools/github/540813873309d1889aa50e0708f10baf90b08890/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.js'; 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/github-tools-test/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/repos-ratelimit-ok.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/github-tools-test/repos') 7 | .query({"type":"all","sort":"updated","per_page":"100"}) 8 | .reply(200, [{ 9 | "id": 191805748, 10 | "node_id": "MDEwOlJlcG9zaXRvcnkxOTE4MDU3NDg=", 11 | "name": "1560446850869-53378", 12 | "full_name": "github-api-tests/1560446850869-53378", 13 | "private": false, 14 | "owner": { 15 | "login": "github-api-tests", 16 | "id": 17532995, 17 | "node_id": "MDEyOk9yZ2FuaXphdGlvbjE3NTMyOTk1", 18 | "avatar_url": "https://avatars3.githubusercontent.com/u/17532995?v=4", 19 | "gravatar_id": "", 20 | "url": "https://api.github.com/users/github-api-tests", 21 | "html_url": "https://github.com/github-api-tests", 22 | "followers_url": "https://api.github.com/users/github-api-tests/followers", 23 | "following_url": "https://api.github.com/users/github-api-tests/following{/other_user}", 24 | "gists_url": "https://api.github.com/users/github-api-tests/gists{/gist_id}", 25 | "starred_url": "https://api.github.com/users/github-api-tests/starred{/owner}{/repo}", 26 | "subscriptions_url": "https://api.github.com/users/github-api-tests/subscriptions", 27 | "organizations_url": "https://api.github.com/users/github-api-tests/orgs", 28 | "repos_url": "https://api.github.com/users/github-api-tests/repos", 29 | "events_url": "https://api.github.com/users/github-api-tests/events{/privacy}", 30 | "received_events_url": "https://api.github.com/users/github-api-tests/received_events", 31 | "type": "Organization", 32 | "site_admin": false 33 | }, 34 | "html_url": "https://github.com/github-api-tests/1560446850869-53378", 35 | "description": "test create organization repo", 36 | "fork": false, 37 | "url": "https://api.github.com/repos/github-api-tests/1560446850869-53378", 38 | "forks_url": "https://api.github.com/repos/github-api-tests/1560446850869-53378/forks", 39 | "keys_url": "https://api.github.com/repos/github-api-tests/1560446850869-53378/keys{/key_id}", 40 | "collaborators_url": "https://api.github.com/repos/github-api-tests/1560446850869-53378/collaborators{/collaborator}", 41 | "teams_url": "https://api.github.com/repos/github-api-tests/1560446850869-53378/teams", 42 | "hooks_url": "https://api.github.com/repos/github-api-tests/1560446850869-53378/hooks", 43 | "issue_events_url": "https://api.github.com/repos/github-api-tests/1560446850869-53378/issues/events{/number}", 44 | "events_url": "https://api.github.com/repos/github-api-tests/1560446850869-53378/events", 45 | "assignees_url": "https://api.github.com/repos/github-api-tests/1560446850869-53378/assignees{/user}", 46 | "branches_url": "https://api.github.com/repos/github-api-tests/1560446850869-53378/branches{/branch}", 47 | "tags_url": "https://api.github.com/repos/github-api-tests/1560446850869-53378/tags", 48 | "blobs_url": "https://api.github.com/repos/github-api-tests/1560446850869-53378/git/blobs{/sha}", 49 | "git_tags_url": "https://api.github.com/repos/github-api-tests/1560446850869-53378/git/tags{/sha}", 50 | "git_refs_url": "https://api.github.com/repos/github-api-tests/1560446850869-53378/git/refs{/sha}", 51 | "trees_url": "https://api.github.com/repos/github-api-tests/1560446850869-53378/git/trees{/sha}", 52 | "statuses_url": "https://api.github.com/repos/github-api-tests/1560446850869-53378/statuses/{sha}", 53 | "languages_url": "https://api.github.com/repos/github-api-tests/1560446850869-53378/languages", 54 | "stargazers_url": "https://api.github.com/repos/github-api-tests/1560446850869-53378/stargazers", 55 | "contributors_url": "https://api.github.com/repos/github-api-tests/1560446850869-53378/contributors", 56 | "subscribers_url": "https://api.github.com/repos/github-api-tests/1560446850869-53378/subscribers", 57 | "subscription_url": "https://api.github.com/repos/github-api-tests/1560446850869-53378/subscription", 58 | "commits_url": "https://api.github.com/repos/github-api-tests/1560446850869-53378/commits{/sha}", 59 | "git_commits_url": "https://api.github.com/repos/github-api-tests/1560446850869-53378/git/commits{/sha}", 60 | "comments_url": "https://api.github.com/repos/github-api-tests/1560446850869-53378/comments{/number}", 61 | "issue_comment_url": "https://api.github.com/repos/github-api-tests/1560446850869-53378/issues/comments{/number}", 62 | "contents_url": "https://api.github.com/repos/github-api-tests/1560446850869-53378/contents/{+path}", 63 | "compare_url": "https://api.github.com/repos/github-api-tests/1560446850869-53378/compare/{base}...{head}", 64 | "merges_url": "https://api.github.com/repos/github-api-tests/1560446850869-53378/merges", 65 | "archive_url": "https://api.github.com/repos/github-api-tests/1560446850869-53378/{archive_format}{/ref}", 66 | "downloads_url": "https://api.github.com/repos/github-api-tests/1560446850869-53378/downloads", 67 | "issues_url": "https://api.github.com/repos/github-api-tests/1560446850869-53378/issues{/number}", 68 | "pulls_url": "https://api.github.com/repos/github-api-tests/1560446850869-53378/pulls{/number}", 69 | "milestones_url": "https://api.github.com/repos/github-api-tests/1560446850869-53378/milestones{/number}", 70 | "notifications_url": "https://api.github.com/repos/github-api-tests/1560446850869-53378/notifications{?since,all,participating}", 71 | "labels_url": "https://api.github.com/repos/github-api-tests/1560446850869-53378/labels{/name}", 72 | "releases_url": "https://api.github.com/repos/github-api-tests/1560446850869-53378/releases{/id}", 73 | "deployments_url": "https://api.github.com/repos/github-api-tests/1560446850869-53378/deployments", 74 | "created_at": "2019-06-13T17:27:33Z", 75 | "updated_at": "2019-06-13T17:27:33Z", 76 | "pushed_at": "2019-06-13T17:27:34Z", 77 | "git_url": "git://github.com/github-api-tests/1560446850869-53378.git", 78 | "ssh_url": "git@github.com:github-api-tests/1560446850869-53378.git", 79 | "clone_url": "https://github.com/github-api-tests/1560446850869-53378.git", 80 | "svn_url": "https://github.com/github-api-tests/1560446850869-53378", 81 | "homepage": "https://github.com/", 82 | "size": 0, 83 | "stargazers_count": 0, 84 | "watchers_count": 0, 85 | "language": null, 86 | "has_issues": true, 87 | "has_projects": true, 88 | "has_downloads": true, 89 | "has_wiki": true, 90 | "has_pages": false, 91 | "forks_count": 0, 92 | "mirror_url": null, 93 | "archived": false, 94 | "disabled": false, 95 | "open_issues_count": 0, 96 | "license": null, 97 | "forks": 0, 98 | "open_issues": 0, 99 | "watchers": 0, 100 | "default_branch": "master" 101 | }], { server: 'GitHub.com', 102 | date: 'Sat, 18 Jun 2016 11:38:49 GMT', 103 | 'content-type': 'application/json; charset=utf-8', 104 | 'content-length': '153901', 105 | connection: 'close', 106 | status: '200 OK', 107 | 'x-ratelimit-limit': '60', 108 | 'x-ratelimit-remaining': '59', 109 | 'x-ratelimit-reset': '1466253529', 110 | 'cache-control': 'public, max-age=60, s-maxage=60', 111 | vary: 'Accept, Accept-Encoding', 112 | etag: '"1901b5d7790ae51d7c5ce6d4b98ac7bc"', 113 | 'x-github-media-type': 'github.v3; format=json', 114 | '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', 115 | 'access-control-allow-origin': '*', 116 | 'content-security-policy': 'default-src \'none\'', 117 | 'strict-transport-security': 'max-age=31536000; includeSubdomains; preload', 118 | 'x-content-type-options': 'nosniff', 119 | 'x-frame-options': 'deny', 120 | 'x-xss-protection': '1; mode=block', 121 | 'x-served-by': 'a51acaae89a7607fd7ee967627be18e4', 122 | 'x-github-request-id': 'AE1408AB:13AFE:4000839:576532C8' }); 123 | return scope; 124 | } 125 | -------------------------------------------------------------------------------- /test/fixtures/user.js: -------------------------------------------------------------------------------- 1 | let user; 2 | 3 | if (process.env.GHTOOLS_USER) { 4 | user = { 5 | 'USERNAME': process.env.GHTOOLS_USER, 6 | 'PASSWORD': process.env.GHTOOLS_PASSWORD, 7 | 'REPO': 'github', 8 | 'ORGANIZATION': 'github-api-tests', 9 | }; 10 | } else { 11 | throw new Error('No testing account set up. Please email jaredrewerts@gmail.com to get access.'); 12 | } 13 | export default user; 14 | -------------------------------------------------------------------------------- /test/gist.spec.js: -------------------------------------------------------------------------------- 1 | import expect from 'must'; 2 | 3 | import Github from '../lib/GitHub'; 4 | import testUser from './fixtures/user.js'; 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 | let revisionId; 14 | 15 | before(function() { 16 | github = new Github({ 17 | username: testUser.USERNAME, 18 | password: testUser.PASSWORD, 19 | auth: 'basic', 20 | }); 21 | }); 22 | 23 | describe('reading', function() { 24 | before(function() { 25 | gist = github.getGist('0ac3ef3451f4bdc9efec660ffce3e336'); 26 | }); 27 | 28 | it('should read a gist', function(done) { 29 | gist.read(assertSuccessful(done, function(err, gist) { 30 | expect(gist).to.have.own('description', testGist.description); 31 | expect(gist.files).to.have.keys(Object.keys(testGist.files)); 32 | expect(gist.files['README.md']).to.have.own('content', testGist.files['README.md'].content); 33 | 34 | done(); 35 | })); 36 | }); 37 | }); 38 | 39 | describe('creating/modifiying', function() { 40 | before(function() { 41 | gist = github.getGist(); 42 | }); 43 | 44 | // 200ms between tests so that Github has a chance to settle 45 | beforeEach(function(done) { 46 | setTimeout(done, 200); 47 | }); 48 | 49 | it('should create gist', function(done) { 50 | gist.create(testGist, assertSuccessful(done, function(err, gist) { 51 | expect(gist).to.have.own('id'); 52 | expect(gist).to.have.own('public', testGist.public); 53 | expect(gist).to.have.own('description', testGist.description); 54 | gistId = gist.id; 55 | revisionId = gist.history[0].version; 56 | 57 | done(); 58 | })); 59 | }); 60 | 61 | it('should star a gist', function(done) { 62 | gist = github.getGist(gistId); 63 | gist.star(assertSuccessful(done, function() { 64 | gist.isStarred(assertSuccessful(done, function(err, result) { 65 | expect(result).to.be(true); 66 | done(); 67 | })); 68 | })); 69 | }); 70 | 71 | it('should create a comment a gist', function(done) { 72 | gist.createComment('Comment test', assertSuccessful(done, function(err, comment) { 73 | expect(comment).to.have.own('body', 'Comment test'); 74 | commentId = comment.id; 75 | done(); 76 | })); 77 | }); 78 | 79 | it('should list comments', function(done) { 80 | gist.listComments(assertSuccessful(done, function(err, comments) { 81 | expect(comments).to.be.an.array(); 82 | done(); 83 | })); 84 | }); 85 | 86 | it('should get comment', function(done) { 87 | gist.getComment(commentId, assertSuccessful(done, function(err, issue) { 88 | expect(issue).to.have.own('id', commentId); 89 | done(); 90 | })); 91 | }); 92 | 93 | it('should edit comment', function(done) { 94 | const newComment = 'new comment test'; 95 | gist.editComment(commentId, newComment, assertSuccessful(done, function(err, comment) { 96 | expect(comment).to.have.own('body', newComment); 97 | done(); 98 | })); 99 | }); 100 | 101 | it('should delete comment', function(done) { 102 | gist.deleteComment(commentId, assertSuccessful(done)); 103 | }); 104 | 105 | it('should update gist', function(done) { 106 | const newGist = { 107 | files: { 108 | 'README.md': { 109 | content: 'README updated', 110 | }, 111 | }, 112 | }; 113 | gist.update(newGist, assertSuccessful(done, function(err, gist) { 114 | expect(gist.history.length).to.be(2); 115 | expect(gist.files['README.md']).to.have.own('content', newGist.files['README.md'].content); 116 | done(); 117 | })); 118 | }); 119 | 120 | it('should list commits', function(done) { 121 | gist.listCommits(assertSuccessful(done, function(err, commits) { 122 | expect(commits).to.be.an.array(); 123 | done(); 124 | })); 125 | }); 126 | 127 | it('should get revision', function(done) { 128 | gist.getRevision(revisionId, assertSuccessful(done, function(err, gist) { 129 | expect(gist.history.length).to.be(1); 130 | expect(gist.files['README.md']).to.have.own('content', testGist.files['README.md'].content); 131 | done(); 132 | })); 133 | }); 134 | }); 135 | 136 | describe('deleting', function() { 137 | before(function() { 138 | gist = github.getGist(gistId); 139 | }); 140 | 141 | // 200ms between tests so that Github has a chance to settle 142 | beforeEach(function(done) { 143 | setTimeout(done, 200); 144 | }); 145 | 146 | it('should delete gist', function(done) { 147 | gist.delete(assertSuccessful(done)); 148 | }); 149 | }); 150 | }); 151 | -------------------------------------------------------------------------------- /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 | 11 | if (cb) { 12 | setTimeout(function delay() { 13 | cb(err, res, xhr); 14 | }, STANDARD_DELAY); 15 | } else { 16 | done(); 17 | } 18 | } catch (e) { 19 | done(e); 20 | } 21 | }; 22 | } 23 | 24 | export function assertFailure(done, cb) { 25 | return function failureCallback(err) { 26 | try { 27 | expect(err).to.exist(); 28 | expect(err).to.have.ownProperty('path'); 29 | expect(err.request).to.exist(); 30 | 31 | if (cb) { 32 | setTimeout(function delay() { 33 | cb(err); 34 | }, STANDARD_DELAY); 35 | } else { 36 | done(); 37 | } 38 | } catch (e) { 39 | done(e); 40 | } 41 | }; 42 | } 43 | 44 | export function assertArray(done) { 45 | return assertSuccessful(done, function isArray(err, result) { 46 | expect(result).to.be.an.array(); 47 | done(); 48 | }); 49 | } 50 | -------------------------------------------------------------------------------- /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/helpers/helperFunctions.js: -------------------------------------------------------------------------------- 1 | export function getNextPage(linksHeader = '') { 2 | const links = linksHeader.split(/\s*,\s*/); // splits and strips the urls 3 | return links.reduce(function(nextUrl, link) { 4 | if (link.search(/rel="next"/) !== -1) { 5 | return (link.match(/<(.*)>/) || [])[1]; 6 | } 7 | 8 | return nextUrl; 9 | }, undefined); 10 | } 11 | 12 | export function deleteRepo(repo, github) { 13 | return github 14 | .getRepo(repo.owner.login, repo.name) 15 | .deleteRepo() 16 | .then((removed) => { 17 | if (removed) { 18 | console.log(repo.full_name, 'deleted'); // eslint-disable-line 19 | } 20 | }); 21 | } 22 | 23 | export function deleteTeam(team, github) { 24 | return github 25 | .getTeam(team.id) 26 | .deleteTeam() 27 | .then((removed) => { 28 | if (removed) { 29 | console.log('team', team.name, 'deleted'); //eslint-disable-line 30 | } 31 | }); 32 | } 33 | 34 | export function deleteProject(project, github) { 35 | return github 36 | .getProject(project.id) 37 | .deleteProject() 38 | .then((removed) => { 39 | if (removed) { 40 | console.log('project', project.name, 'deleted'); //eslint-disable-line 41 | } 42 | }); 43 | } 44 | -------------------------------------------------------------------------------- /test/helpers/wait.js: -------------------------------------------------------------------------------- 1 | export default function(delay = 1000) { 2 | return () => new Promise((resolve) => { 3 | setTimeout(() => resolve(), delay); 4 | }); 5 | } 6 | -------------------------------------------------------------------------------- /test/issue.spec.js: -------------------------------------------------------------------------------- 1 | import expect from 'must'; 2 | 3 | import Github from '../lib/GitHub'; 4 | import testUser from './fixtures/user.js'; 5 | import wait from './helpers/wait'; 6 | import {assertSuccessful} from './helpers/callbacks'; 7 | import getTestRepoName from './helpers/getTestRepoName'; 8 | 9 | describe('Issue', function() { 10 | let github; 11 | const testRepoName = getTestRepoName(); 12 | let remoteIssues; 13 | 14 | before(function() { 15 | github = new Github({ 16 | username: testUser.USERNAME, 17 | password: testUser.PASSWORD, 18 | auth: 'basic', 19 | }); 20 | 21 | return github 22 | .getUser() 23 | .createRepo({name: testRepoName}) 24 | .then(wait(5000)) 25 | .then(function() { 26 | remoteIssues = github.getIssues(testUser.USERNAME, testRepoName); 27 | return remoteIssues.createIssue({ 28 | title: 'Test issue', 29 | body: 'Test issue body', 30 | }); 31 | }) 32 | .then(function() { 33 | return remoteIssues.createMilestone({ 34 | title: 'Default Milestone', 35 | description: 'Test', 36 | }); 37 | }); 38 | }); 39 | 40 | after(function() { 41 | return github.getRepo(testUser.USERNAME, testRepoName).deleteRepo(); 42 | }); 43 | 44 | describe('reading', function() { 45 | let remoteIssueId; 46 | let milestoneId; 47 | 48 | it('should list issues', function(done) { 49 | remoteIssues.listIssues({}, assertSuccessful(done, function(err, issues) { 50 | expect(issues).to.be.an.array(); 51 | remoteIssueId = issues[0].number; 52 | 53 | done(); 54 | })); 55 | }); 56 | 57 | it('should get issue', function(done) { 58 | remoteIssues.getIssue(remoteIssueId, assertSuccessful(done, function(err, issue) { 59 | expect(issue).to.have.own('number', remoteIssueId); 60 | 61 | done(); 62 | })); 63 | }); 64 | 65 | it('should get issue events', function() { 66 | return remoteIssues.listIssueEvents(remoteIssueId) 67 | .then(function({data: events}) { 68 | expect(events).to.be.an.array(); 69 | }); 70 | }); 71 | 72 | it('should get all milestones', function(done) { 73 | remoteIssues.listMilestones() 74 | .then(({data: milestones}) => { 75 | expect(milestones).to.be.an.array(); 76 | milestoneId = milestones[0].number; 77 | 78 | done(); 79 | }).catch(done); 80 | }); 81 | 82 | it('should get a single milestone', function(done) { 83 | remoteIssues.getMilestone(milestoneId) 84 | .then(({data: milestone}) => { 85 | expect(milestone).to.have.own('title', 'Default Milestone'); 86 | done(); 87 | }).catch(done); 88 | }); 89 | }); 90 | 91 | describe('creating/editing/deleting', function() { 92 | let createdIssueId; 93 | let issueCommentId; 94 | let createdMilestoneId; 95 | let createdLabel; 96 | 97 | // 200ms between tests so that Github has a chance to settle 98 | beforeEach(function(done) { 99 | setTimeout(done, 200); 100 | }); 101 | 102 | it('should create issue', function(done) { 103 | const newIssue = { 104 | title: 'New issue', 105 | body: 'New issue body', 106 | }; 107 | 108 | remoteIssues.createIssue(newIssue, assertSuccessful(done, function(err, issue) { 109 | createdIssueId = issue.number; 110 | expect(issue).to.have.own('url'); 111 | expect(issue).to.have.own('title', newIssue.title); 112 | expect(issue).to.have.own('body', newIssue.body); 113 | 114 | done(); 115 | })); 116 | }); 117 | 118 | it('should edit issue', function(done) { 119 | const newProps = { 120 | title: 'Edited title', 121 | state: 'closed', 122 | }; 123 | 124 | remoteIssues.editIssue(createdIssueId, newProps, assertSuccessful(done, function(err, issue) { 125 | expect(issue).to.have.own('title', newProps.title); 126 | 127 | done(); 128 | })); 129 | }); 130 | 131 | it('should post issue comment', function(done) { 132 | remoteIssues.createIssueComment(createdIssueId, 'Comment test', assertSuccessful(done, function(err, issue) { 133 | expect(issue).to.have.own('body', 'Comment test'); 134 | 135 | done(); 136 | })); 137 | }); 138 | 139 | it('should list issue comments', function(done) { 140 | remoteIssues.listIssueComments(createdIssueId, assertSuccessful(done, function(err, comments) { 141 | expect(comments).to.be.an.array(); 142 | expect(comments[0]).to.have.own('body', 'Comment test'); 143 | issueCommentId = comments[0].id; 144 | done(); 145 | })); 146 | }); 147 | 148 | it('should get a single issue comment', function(done) { 149 | remoteIssues.getIssueComment(issueCommentId, assertSuccessful(done, function(err, comment) { 150 | expect(comment).to.have.own('body', 'Comment test'); 151 | done(); 152 | })); 153 | }); 154 | 155 | it('should edit issue comment', function(done) { 156 | remoteIssues.editIssueComment(issueCommentId, 'Comment test edited', 157 | assertSuccessful(done, function(err, comment) { 158 | expect(comment).to.have.own('body', 'Comment test edited'); 159 | 160 | done(); 161 | })); 162 | }); 163 | 164 | it('should delete issue comment', function(done) { 165 | remoteIssues.deleteIssueComment(issueCommentId, assertSuccessful(done, function(err, response) { 166 | expect(response).to.be.true(); 167 | 168 | done(); 169 | })); 170 | }); 171 | 172 | it('should create a milestone', function(done) { 173 | let milestone = { 174 | title: 'v42', 175 | description: 'The ultimate version', 176 | }; 177 | 178 | remoteIssues.createMilestone(milestone) 179 | .then(({data: createdMilestone}) => { 180 | expect(createdMilestone).to.have.own('number'); 181 | expect(createdMilestone).to.have.own('title', milestone.title); 182 | 183 | createdMilestoneId = createdMilestone.number; 184 | done(); 185 | }).catch(done); 186 | }); 187 | it('should update a milestone', function(done) { 188 | let milestone = { 189 | description: 'Version 6 * 7', 190 | }; 191 | 192 | expect(createdMilestoneId).to.be.a.number(); 193 | remoteIssues.editMilestone(createdMilestoneId, milestone) 194 | .then(({data: createdMilestone}) => { 195 | expect(createdMilestone).to.have.own('number', createdMilestoneId); 196 | expect(createdMilestone).to.have.own('description', milestone.description); 197 | 198 | done(); 199 | }).catch(done); 200 | }); 201 | it('should delete a milestone', function(done) { 202 | expect(createdMilestoneId).to.be.a.number(); 203 | remoteIssues.deleteMilestone(createdMilestoneId) 204 | .then(({status}) => { 205 | expect(status).to.equal(204); 206 | done(); 207 | }).catch(done); 208 | }); 209 | 210 | it('should create a label', (done) => { 211 | let label = { 212 | name: 'test', 213 | color: '123456', 214 | }; 215 | 216 | remoteIssues.createLabel(label) 217 | .then(({data: _createdLabel}) => { 218 | expect(_createdLabel).to.have.own('name', label.name); 219 | expect(_createdLabel).to.have.own('color', label.color); 220 | 221 | createdLabel = label.name; 222 | done(); 223 | }).catch(done); 224 | }); 225 | 226 | it('should retrieve a single label', (done) => { 227 | let label = { 228 | name: 'test', 229 | color: '123456', 230 | }; 231 | 232 | remoteIssues.getLabel(label.name) 233 | .then(({data: retrievedLabel}) => { 234 | expect(retrievedLabel).to.have.own('name', label.name); 235 | expect(retrievedLabel).to.have.own('color', label.color); 236 | 237 | done(); 238 | }).catch(done); 239 | }); 240 | 241 | it('should update a label', (done) => { 242 | let label = { 243 | color: '789abc', 244 | name: createdLabel, 245 | }; 246 | 247 | expect(createdLabel).to.be.a.string(); 248 | remoteIssues.editLabel(createdLabel, label) 249 | .then(({data: updatedLabel}) => { 250 | expect(updatedLabel).to.have.own('name', createdLabel); 251 | expect(updatedLabel).to.have.own('color', label.color); 252 | 253 | done(); 254 | }).catch(done); 255 | }); 256 | 257 | it('should list labels', (done) => { 258 | expect(createdLabel).to.be.a.string(); 259 | 260 | remoteIssues.listLabels({}, assertSuccessful(done, function(err, labels) { 261 | expect(labels).to.be.an.array(); 262 | const hasLabel = labels.some((label) => label.name === createdLabel); 263 | expect(hasLabel).to.be.true(); 264 | done(); 265 | })); 266 | }); 267 | 268 | it('should delete a label', (done) => { 269 | expect(createdLabel).to.be.a.string(); 270 | remoteIssues.deleteLabel(createdLabel) 271 | .then(({status}) => { 272 | expect(status).to.equal(204); 273 | done(); 274 | }).catch(done); 275 | }); 276 | }); 277 | }); 278 | -------------------------------------------------------------------------------- /test/markdown.spec.js: -------------------------------------------------------------------------------- 1 | import expect from 'must'; 2 | 3 | import Github from '../lib/GitHub'; 4 | import testUser from './fixtures/user.js'; 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 gollum#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.js'; 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 | let createdProject; 13 | 14 | before(function() { 15 | github = new Github({ 16 | username: testUser.USERNAME, 17 | password: testUser.PASSWORD, 18 | auth: 'basic', 19 | }); 20 | createdProject = undefined; 21 | }); 22 | 23 | after(function() { 24 | if (createdProject) { 25 | return github.getProject(createdProject.id).deleteProject(); 26 | } 27 | }); 28 | 29 | describe('reading', function() { 30 | let organization; 31 | 32 | before(function() { 33 | organization = github.getOrganization(ORG_NAME); 34 | }); 35 | 36 | it('should show user\'s organisation repos', function(done) { 37 | organization.getRepos(assertArray(done)); 38 | }); 39 | 40 | it('should list the users in the organization', function(done) { 41 | organization.listMembers() 42 | .then(function({data: members}) { 43 | expect(members).to.be.an.array(); 44 | 45 | const hasClayReimann = members.some((member) => member.login === MEMBER_NAME); 46 | expect(hasClayReimann).to.be.true(); 47 | 48 | done(); 49 | }).catch(done); 50 | }); 51 | 52 | it('should test for membership', function() { 53 | return organization.isMember(MEMBER_NAME) 54 | .then(function(isMember) { 55 | expect(isMember).to.be.true(); 56 | }); 57 | }); 58 | }); 59 | 60 | describe('creating/updating', function() { 61 | let organization; 62 | const testRepoName = getTestRepoName(); 63 | 64 | before(function() { 65 | organization = github.getOrganization(testUser.ORGANIZATION); 66 | }); 67 | 68 | it('should create an organization repo', function(done) { 69 | const options = { 70 | name: testRepoName, 71 | description: 'test create organization repo', 72 | homepage: 'https://github.com/', 73 | private: false, 74 | has_issues: true, // eslint-disable-line 75 | has_wiki: true, // eslint-disable-line 76 | has_downloads: true // eslint-disable-line 77 | }; 78 | 79 | organization.createRepo(options, assertSuccessful(done, function(err, repo) { 80 | expect(repo.name).to.equal(testRepoName); 81 | expect(repo.full_name).to.equal(`${testUser.ORGANIZATION}/${testRepoName}`); // eslint-disable-line 82 | done(); 83 | })); 84 | }); 85 | 86 | it('should create an organization team', function(done) { 87 | const options = { 88 | name: testRepoName, 89 | description: 'Created by unit tests', 90 | privacy: 'secret', 91 | }; 92 | 93 | organization.createTeam(options, assertSuccessful(done, function(err, team) { 94 | expect(team.name).to.equal(testRepoName); 95 | expect(team.organization.login).to.equal(testUser.ORGANIZATION); // jscs:ignore 96 | done(); 97 | })); 98 | }); 99 | 100 | it('should list the teams in the organization', function() { 101 | return organization.getTeams() 102 | .then(({data}) => { 103 | const hasTeam = data.some((member) => member.slug === testRepoName); 104 | 105 | expect(hasTeam).to.be.true(); 106 | }); 107 | }); 108 | 109 | it('should create a project', function(done) { 110 | organization.createProject({ 111 | name: testRepoName, 112 | body: 'body', 113 | }, assertSuccessful(done, function(err, project) { 114 | createdProject = project; 115 | expect(project).to.own('name', testRepoName); 116 | expect(project).to.own('body', 'body'); 117 | done(); 118 | })); 119 | }); 120 | 121 | it('should list repo projects', function(done) { 122 | organization.listProjects(assertSuccessful(done, function(err, projects) { 123 | expect(projects).to.be.an.array(); 124 | 125 | const hasProject = projects.some((project) => project.name === testRepoName); 126 | 127 | expect(hasProject).to.be.true(); 128 | done(); 129 | })); 130 | }); 131 | }); 132 | }); 133 | -------------------------------------------------------------------------------- /test/project.spec.js: -------------------------------------------------------------------------------- 1 | import expect from 'must'; 2 | 3 | import Github from '../lib/GitHub'; 4 | import wait from './helpers/wait'; 5 | import testUser from './fixtures/user.js'; 6 | import {assertSuccessful} from './helpers/callbacks'; 7 | import getTestRepoName from './helpers/getTestRepoName'; 8 | 9 | describe('Project', function() { 10 | let github; 11 | const testRepoName = getTestRepoName(); 12 | let project; 13 | let columnId; 14 | let cardId; 15 | 16 | before(function() { 17 | github = new Github({ 18 | username: testUser.USERNAME, 19 | password: testUser.PASSWORD, 20 | auth: 'basic', 21 | }); 22 | 23 | return github 24 | .getUser() 25 | .createRepo({name: testRepoName}) 26 | .then(wait(5000)) 27 | .then(function() { 28 | const remoteRepo = github.getRepo(testUser.USERNAME, testRepoName); 29 | return remoteRepo.createProject({ 30 | name: 'test-project', 31 | body: 'body', 32 | }); 33 | }) 34 | .then(function(_project) { 35 | project = github.getProject(_project.data.id); 36 | }); 37 | }); 38 | 39 | after(function() { 40 | return github.getRepo(testUser.USERNAME, testRepoName).deleteRepo(); 41 | }); 42 | 43 | it('should get repo project', function(done) { 44 | project.getProject(assertSuccessful(done, function(err, project) { 45 | expect(project).to.own('name', 'test-project'); 46 | done(); 47 | })); 48 | }); 49 | 50 | it('should update a project', function(done) { 51 | project.updateProject({ 52 | name: 'another-test-project', 53 | body: 'another-body', 54 | }, assertSuccessful(done, function(err, project) { 55 | expect(project).to.own('name', 'another-test-project'); 56 | expect(project).to.own('body', 'another-body'); 57 | done(); 58 | })); 59 | }); 60 | 61 | it('should create a repo project column', function(done) { 62 | project.createProjectColumn({ 63 | name: 'test-column', 64 | }, assertSuccessful(done, function(err, column) { 65 | expect(column).to.own('name', 'test-column'); 66 | columnId = column.id; 67 | done(); 68 | })); 69 | }); 70 | 71 | it('should list repo project columns', function(done) { 72 | project.listProjectColumns(assertSuccessful(done, function(err, columns) { 73 | expect(columns).to.be.an.array(); 74 | expect(columns.length).to.equal(1); 75 | done(); 76 | })); 77 | }); 78 | 79 | it('should get repo project column', function(done) { 80 | project.getProjectColumn(columnId, assertSuccessful(done, function(err, project) { 81 | expect(project).to.own('name', 'test-column'); 82 | done(); 83 | })); 84 | }); 85 | 86 | it('should update a repo project column', function(done) { 87 | project.updateProjectColumn(columnId, { 88 | name: 'another-test-column', 89 | }, assertSuccessful(done, function(err, column) { 90 | expect(column).to.own('name', 'another-test-column'); 91 | done(); 92 | })); 93 | }); 94 | 95 | it('should create repo project card', function(done) { 96 | project.createProjectCard(columnId, { 97 | note: 'test-card', 98 | }, assertSuccessful(done, function(err, card) { 99 | expect(card).to.own('note', 'test-card'); 100 | cardId = card.id; 101 | done(); 102 | })); 103 | }); 104 | 105 | it('should list cards of a project', function(done) { 106 | project.listProjectCards(assertSuccessful(done, function(err, cards) { 107 | expect(cards).to.be.an.array(); 108 | expect(cards.length).to.equal(1); 109 | done(); 110 | })); 111 | }); 112 | 113 | it('should list cards of a column', function(done) { 114 | project.listColumnCards(columnId, assertSuccessful(done, function(err, cards) { 115 | expect(cards).to.be.an.array(); 116 | expect(cards.length).to.equal(1); 117 | done(); 118 | })); 119 | }); 120 | 121 | it('should get repo project card', function(done) { 122 | project.getProjectCard(cardId, assertSuccessful(done, function(err, card) { 123 | expect(card).to.own('note', 'test-card'); 124 | done(); 125 | })); 126 | }); 127 | 128 | it('should update repo project card', function(done) { 129 | project.updateProjectCard(cardId, { 130 | note: 'another-test-card', 131 | }, assertSuccessful(done, function(err, card) { 132 | expect(card).to.own('note', 'another-test-card'); 133 | done(); 134 | })); 135 | }); 136 | 137 | it('should move repo project card', function(done) { 138 | project.moveProjectCard(cardId, 'top', columnId, assertSuccessful(done, function(err, result) { 139 | expect(result).to.be(true); 140 | done(); 141 | })); 142 | }); 143 | 144 | it('should move repo project column', function(done) { 145 | project.moveProjectColumn(columnId, 'first', assertSuccessful(done, function(err, result) { 146 | expect(result).to.be(true); 147 | done(); 148 | })); 149 | }); 150 | 151 | it('should delete repo project card', function(done) { 152 | project.deleteProjectCard(cardId, assertSuccessful(done, function(err, result) { 153 | expect(result).to.be(true); 154 | done(); 155 | })); 156 | }); 157 | 158 | it('should delete repo project column', function(done) { 159 | project.deleteProjectColumn(columnId, assertSuccessful(done, function(err, result) { 160 | expect(result).to.be(true); 161 | done(); 162 | })); 163 | }); 164 | 165 | it('should delete repo project', function(done) { 166 | project.deleteProject(assertSuccessful(done, function(err, result) { 167 | expect(result).to.be(true); 168 | done(); 169 | })); 170 | }); 171 | }); 172 | -------------------------------------------------------------------------------- /test/rate-limit.spec.js: -------------------------------------------------------------------------------- 1 | import expect from 'must'; 2 | 3 | import Github from '../lib/GitHub'; 4 | import testUser from './fixtures/user.js'; 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 wait from './helpers/wait'; 5 | import testUser from './fixtures/user.js'; 6 | import loadImage from './fixtures/imageBlob'; 7 | import {assertSuccessful, assertFailure} from './helpers/callbacks'; 8 | import getTestRepoName from './helpers/getTestRepoName'; 9 | 10 | describe('Repository', function() { 11 | let github; 12 | let user; 13 | let imageB64; 14 | let imageBlob; 15 | const testRepoName = getTestRepoName(); 16 | const v10SHA = '20fcff9129005d14cc97b9d59b8a3d37f4fb633b'; 17 | const statusUrl = 18 | 'https://api.github.com/repos/github-tools/github/statuses/20fcff9129005d14cc97b9d59b8a3d37f4fb633b'; 19 | 20 | before(function(done) { 21 | github = new Github({ 22 | username: testUser.USERNAME, 23 | password: testUser.PASSWORD, 24 | }); 25 | 26 | loadImage(function(b64, blob) { 27 | imageB64 = b64; 28 | imageBlob = blob; 29 | done(); 30 | }); 31 | }); 32 | 33 | describe('reading', function() { 34 | let remoteRepo; 35 | 36 | before(function() { 37 | remoteRepo = github.getRepo('github-tools', 'github'); 38 | }); 39 | 40 | it('should get repo details', function(done) { 41 | remoteRepo.getDetails(assertSuccessful(done, function(err, repo) { 42 | expect(repo).to.have.own('full_name', 'github-tools/github'); 43 | 44 | done(); 45 | })); 46 | }); 47 | 48 | it('should get blob', function(done) { 49 | remoteRepo.getSha('master', 'README.md', assertSuccessful(done, function(err, response) { 50 | remoteRepo.getBlob(response.sha, assertSuccessful(done, function(err, content) { 51 | expect(content).to.be.include('# Github.js'); 52 | 53 | done(); 54 | })); 55 | })); 56 | }); 57 | 58 | it('should get a branch', function(done) { 59 | remoteRepo.getBranch('master', assertSuccessful(done, function(err, content) { 60 | expect(content.name).to.be('master'); 61 | 62 | done(); 63 | })); 64 | }); 65 | 66 | it('should show repo contents', function(done) { 67 | remoteRepo.getContents('master', '', false, assertSuccessful(done, function(err, contents) { 68 | expect(contents).to.be.an.array(); 69 | 70 | const readme = contents.filter(function(content) { 71 | return content.path === 'README.md'; 72 | }); 73 | 74 | expect(readme).to.have.length(1); 75 | expect(readme[0]).to.have.own('type', 'file'); 76 | 77 | done(); 78 | })); 79 | }); 80 | 81 | it('should show repo content raw', function(done) { 82 | remoteRepo.getContents('master', 'README.md', 'raw', assertSuccessful(done, function(err, text) { 83 | expect(text).to.contain('# Github.js'); 84 | 85 | done(); 86 | })); 87 | }); 88 | 89 | it('should show repo readme', function(done) { 90 | remoteRepo.getReadme('master', 'raw', assertSuccessful(done, function(err, text) { 91 | expect(text).to.contain('# Github.js'); 92 | 93 | done(); 94 | })); 95 | }); 96 | 97 | it('should get ref from repo', function(done) { 98 | remoteRepo.getRef('heads/master', assertSuccessful(done)); 99 | }); 100 | 101 | it('should get tree', function(done) { 102 | remoteRepo.getTree('master', assertSuccessful(done, function(err, response) { 103 | let {tree} = response; 104 | expect(tree).to.be.an.array(); 105 | expect(tree.length).to.be.above(0); 106 | 107 | done(); 108 | })); 109 | }); 110 | 111 | it('should fork repo', function(done) { 112 | remoteRepo.fork(assertSuccessful(done)); 113 | }); 114 | 115 | it('should fork repo to org', function(done) { 116 | remoteRepo.forkToOrg(testUser.ORGANIZATION, assertSuccessful(done)); 117 | }); 118 | 119 | it('should list forks of repo', function(done) { 120 | remoteRepo.listForks(assertSuccessful(done, function(err, forks) { 121 | expect(forks).to.be.an.array(); 122 | expect(forks.length).to.be.above(0); 123 | done(); 124 | })); 125 | }); 126 | 127 | it('should list commits with no options', function(done) { 128 | remoteRepo.listCommits(assertSuccessful(done, function(err, commits) { 129 | expect(commits).to.be.an.array(); 130 | expect(commits.length).to.be.above(0); 131 | 132 | expect(commits[0]).to.have.own('commit'); 133 | expect(commits[0]).to.have.own('author'); 134 | 135 | done(); 136 | })); 137 | }); 138 | 139 | it('should list commits with null options', function(done) { 140 | remoteRepo.listCommits(null, assertSuccessful(done, function(err, commits) { 141 | expect(commits).to.be.an.array(); 142 | expect(commits.length).to.be.above(0); 143 | 144 | expect(commits[0]).to.have.own('commit'); 145 | expect(commits[0]).to.have.own('author'); 146 | 147 | done(); 148 | })); 149 | }); 150 | 151 | it('should list commits with all options', function(done) { 152 | const since = new Date(2015, 0, 1); 153 | const until = new Date(2016, 0, 20); 154 | const options = { 155 | sha: 'master', 156 | path: 'test', 157 | author: 'AurelioDeRosa', 158 | since, 159 | until, 160 | }; 161 | 162 | remoteRepo.listCommits(options, assertSuccessful(done, function(err, commits) { 163 | expect(commits).to.be.an.array(); 164 | expect(commits.length).to.be.above(0); 165 | 166 | const commit = commits[0]; 167 | const commitDate = new Date(commit.commit.author.date); 168 | 169 | expect(commit).to.have.own('commit'); 170 | expect(commit.author).to.have.own('login', 'AurelioDeRosa'); 171 | expect(commitDate.getTime()).to.be.between(since.getTime(), until.getTime()); 172 | done(); 173 | })); 174 | }); 175 | 176 | it('should list commits on a PR with no options', function(done) { 177 | const PR_NUMBER = 588; 178 | remoteRepo.listCommitsOnPR(PR_NUMBER, assertSuccessful(done, function(err, commits) { 179 | expect(commits).to.be.an.array(); 180 | expect(commits.length).to.be.equal(2); 181 | 182 | let message1 = 'fix(repository): prevents lib from crashing when not providing optional arguments'; 183 | expect(commits[0].author).to.have.own('login', 'hazmah0'); 184 | expect(commits[0].commit).to.have.own('message', message1); 185 | 186 | let message2 = 'test(repository): updates test to use promise instead of callback'; 187 | expect(commits[1].author).to.have.own('login', 'hazmah0'); 188 | expect(commits[1].commit).to.have.own('message', message2); 189 | 190 | done(); 191 | })); 192 | }); 193 | 194 | it('should get the latest commit from master', function(done) { 195 | remoteRepo.getSingleCommit('master', assertSuccessful(done, function(err, commit) { 196 | expect(commit).to.have.own('sha'); 197 | expect(commit).to.have.own('commit'); 198 | expect(commit).to.have.own('author'); 199 | 200 | done(); 201 | })); 202 | }); 203 | 204 | it('should fail when null ref is passed', function(done) { 205 | remoteRepo.getSingleCommit(null, assertFailure(done, function(err) { 206 | expect(err.response.status).to.be(422); 207 | done(); 208 | })); 209 | }); 210 | 211 | it('should show repo contributors', function(done) { 212 | remoteRepo.getContributors(assertSuccessful(done, function(err, contributors) { 213 | if (!(contributors instanceof Array)) { 214 | console.log(JSON.stringify(contributors, null, 2)); // eslint-disable-line 215 | } 216 | expect(contributors).to.be.an.array(); 217 | expect(contributors.length).to.be.above(1); 218 | 219 | const contributor = contributors[0]; 220 | 221 | expect(contributor).to.have.own('login'); 222 | expect(contributor).to.have.own('contributions'); 223 | 224 | done(); 225 | })); 226 | }); 227 | 228 | it('should show repo contributor stats', function(done) { 229 | remoteRepo.getContributorStats(assertSuccessful(done, function(err, contributors) { 230 | if (!(contributors instanceof Array)) { 231 | console.log(JSON.stringify(contributors, null, 2)); // eslint-disable-line 232 | } 233 | expect(contributors).to.be.an.array(); 234 | expect(contributors.length).to.be.above(1); 235 | 236 | const contributor = contributors[0]; 237 | 238 | expect(contributor).to.have.own('author'); 239 | expect(contributor).to.have.own('total'); 240 | expect(contributor).to.have.own('weeks'); 241 | 242 | done(); 243 | })); 244 | }); 245 | 246 | // @TODO repo.branch, repo.pull 247 | 248 | it('should list repo branches', function(done) { 249 | remoteRepo.listBranches(assertSuccessful(done)); 250 | }); 251 | 252 | it('should get commit from repo', function(done) { 253 | remoteRepo.getCommit(v10SHA, assertSuccessful(done, function(err, commit) { 254 | expect(commit.message).to.equal('v0.10.4'); 255 | expect(commit.author.date).to.equal('2015-03-20T17:01:42Z'); 256 | 257 | done(); 258 | })); 259 | }); 260 | 261 | it('should get statuses for a SHA from a repo', function(done) { 262 | remoteRepo.listStatuses(v10SHA, assertSuccessful(done, function(err, statuses) { 263 | expect(statuses).to.be.an.array(); 264 | expect(statuses.length).to.equal(6); 265 | 266 | const correctUrls = statuses.every(function(status) { 267 | return status.url === statusUrl; 268 | }); 269 | 270 | expect(correctUrls).to.be(true); 271 | 272 | done(); 273 | })); 274 | }); 275 | 276 | it('should get combined view of commit statuses for a SHA from a repo', function(done) { 277 | remoteRepo.getCombinedStatus(v10SHA, assertSuccessful(done, function(err, combinedStatusesView) { 278 | expect(combinedStatusesView.sha).to.equal(v10SHA); 279 | expect(combinedStatusesView.state).to.equal('success'); 280 | expect(combinedStatusesView.statuses[0].context).to.equal('continuous-integration/travis-ci/push'); 281 | expect(combinedStatusesView.total_count).to.equal(1); 282 | 283 | done(); 284 | })); 285 | }); 286 | 287 | it('should get a SHA from a repo', function(done) { 288 | remoteRepo.getSha('master', '.gitignore', assertSuccessful(done)); 289 | }); 290 | 291 | it('should get a repo by fullname', function(done) { 292 | const repoByName = github.getRepo('github-tools/github'); 293 | 294 | repoByName.getDetails(assertSuccessful(done, function(err, repo) { 295 | expect(repo).to.have.own('full_name', 'github-tools/github'); 296 | 297 | done(); 298 | })); 299 | }); 300 | 301 | it('should check if the repo is starred', function(done) { 302 | remoteRepo.isStarred(assertSuccessful(done, function(err, result) { 303 | expect(result).to.equal(false); 304 | 305 | done(); 306 | })); 307 | }); 308 | }); 309 | 310 | describe('creating/modifiying', function() { 311 | const fileName = 'test.md'; 312 | 313 | const initialText = 'This is a test.'; 314 | const initialMessage = 'This is my 44 character long commit message.'; 315 | 316 | const updatedText = 'This file has been updated.'; 317 | const updatedMessage = 'This is my 51 character long update commit message.'; 318 | 319 | const fileToDelete = 'tmp.md'; 320 | const deleteMessage = 'This is my 51 character long delete commit message.'; 321 | 322 | const unicodeFileName = '\u4E2D\u6587\u6D4B\u8BD5.md'; 323 | const unicodeText = '\u00A1\u00D3i de m\u00ED, que verg\u00FCenza!'; 324 | const unicodeMessage = 'Such na\u00EFvet\u00E9\u2026'; 325 | 326 | const imageFileName = 'image.png'; 327 | 328 | const releaseTag = 'foo'; 329 | const releaseName = 'My awesome release'; 330 | const releaseBody = 'This is my 49 character long release description.'; 331 | let sha; 332 | let releaseId; 333 | let remoteRepo; 334 | 335 | before(function() { 336 | user = github.getUser(); 337 | remoteRepo = github.getRepo(testUser.USERNAME, testRepoName); 338 | }); 339 | 340 | // 200ms between tests so that Github has a chance to settle 341 | beforeEach(function(done) { 342 | setTimeout(done, 200); 343 | }); 344 | 345 | it('should create repo', function(done) { 346 | const repoDef = { 347 | name: testRepoName, 348 | }; 349 | 350 | user.createRepo(repoDef, assertSuccessful(done, function(err, repo) { 351 | expect(repo).to.have.own('name', testRepoName); 352 | 353 | done(); 354 | })); 355 | }); 356 | 357 | it('should be able to edit repository information', function(done) { 358 | const options = { 359 | name: testRepoName, 360 | description: 'New short description', 361 | homepage: 'http://example.com', 362 | }; 363 | 364 | remoteRepo.updateRepository(options, assertSuccessful(done, 365 | function(err, repository) { 366 | expect(repository).to.have.own('homepage', options.homepage); 367 | expect(repository).to.have.own('description', options.description); 368 | expect(repository).to.have.own('name', testRepoName); 369 | done(); 370 | })); 371 | }); 372 | 373 | it('should show repo collaborators', function(done) { 374 | remoteRepo.getCollaborators(assertSuccessful(done, function(err, collaborators) { 375 | if (!(collaborators instanceof Array)) { 376 | console.log(JSON.stringify(collaborators, null, 2)); // eslint-disable-line 377 | } 378 | expect(collaborators).to.be.an.array(); 379 | expect(collaborators).to.have.length(1); 380 | 381 | const collaborator = collaborators[0]; 382 | 383 | expect(collaborator).to.have.own('login', testUser.USERNAME); 384 | expect(collaborator).to.have.own('id'); 385 | expect(collaborator).to.have.own('permissions'); 386 | 387 | done(); 388 | })); 389 | }); 390 | 391 | it('should test whether user is collaborator', function(done) { 392 | remoteRepo.isCollaborator(testUser.USERNAME, assertSuccessful(done)); 393 | }); 394 | 395 | it('should write to repo', function(done) { 396 | remoteRepo.writeFile('master', fileName, initialText, initialMessage, assertSuccessful(done, function() { 397 | wait()().then(() => remoteRepo.getContents('master', fileName, 'raw', 398 | assertSuccessful(done, function(err, fileText) { 399 | expect(fileText).to.be(initialText); 400 | 401 | done(); 402 | }))); 403 | })); 404 | }); 405 | 406 | it('should successfully write to repo when not providing optional options argument', function(done) { 407 | const promise = remoteRepo.writeFile('master', fileName, initialText, initialMessage); 408 | promise.then(() => remoteRepo.getContents('master', fileName, 'raw', 409 | assertSuccessful(done, function(err, fileText) { 410 | expect(fileText).to.be(initialText); 411 | 412 | done(); 413 | }))); 414 | }); 415 | 416 | it('should rename files', function(done) { 417 | remoteRepo.writeFile('master', fileName, initialText, initialMessage, assertSuccessful(done, function() { 418 | wait()().then(() => remoteRepo.move('master', fileName, 'new_name', assertSuccessful(done, function() { 419 | wait()().then(() => remoteRepo.getContents('master', fileName, 'raw', assertFailure(done, function(err) { 420 | expect(err.response.status).to.be(404); 421 | remoteRepo.getContents('master', 'new_name', 'raw', assertSuccessful(done, function(err, fileText) { 422 | expect(fileText).to.be(initialText); 423 | 424 | done(); 425 | })); 426 | }))); 427 | }))); 428 | })); 429 | }); 430 | 431 | it('should create a new branch', function(done) { 432 | remoteRepo.createBranch('master', 'dev', assertSuccessful(done, function(err, branch) { 433 | expect(branch).to.have.property('ref', 'refs/heads/dev'); 434 | expect(branch.object).to.have.property('sha'); 435 | 436 | done(); 437 | })); 438 | }); 439 | 440 | it('should write to repo branch', function(done) { 441 | remoteRepo.writeFile('dev', fileName, updatedText, updatedMessage, assertSuccessful(done, function() { 442 | remoteRepo.getContents('dev', fileName, 'raw', assertSuccessful(done, function(err, fileText) { 443 | expect(fileText).to.be(updatedText); 444 | 445 | done(); 446 | })); 447 | })); 448 | }); 449 | 450 | it('should compare two branches', function(done) { 451 | remoteRepo.createBranch('master', 'compare', assertSuccessful(done, function() { 452 | remoteRepo.writeFile('compare', fileName, updatedText, updatedMessage, assertSuccessful(done, function() { 453 | remoteRepo.compareBranches('master', 'compare', assertSuccessful(done, function(err, diff) { 454 | expect(diff).to.have.own('total_commits', 1); 455 | expect(diff.files[0]).to.have.own('filename', fileName); 456 | 457 | done(); 458 | })); 459 | })); 460 | })); 461 | }); 462 | 463 | it('should submit a pull request', function(done) { 464 | const base = 'master'; 465 | const head = 'pull-request'; 466 | const title = 'Test pull request'; 467 | const body = 'This is a test pull request'; 468 | const prSpec = {title, body, base, head}; 469 | 470 | remoteRepo.createBranch(base, head, assertSuccessful(done, function() { 471 | remoteRepo.writeFile(head, fileName, updatedText, updatedMessage, assertSuccessful(done, function() { 472 | remoteRepo.createPullRequest(prSpec, assertSuccessful(done, function(err, pullRequest) { 473 | expect(pullRequest).to.have.own('number'); 474 | expect(pullRequest.number).to.be.above(0); 475 | expect(pullRequest).to.have.own('title', title); 476 | expect(pullRequest).to.have.own('body', body); 477 | 478 | done(); 479 | })); 480 | })); 481 | })); 482 | }); 483 | 484 | it('should create ref on repo', function(done) { 485 | remoteRepo.getRef('heads/master', assertSuccessful(done, function(err, refSpec) { 486 | let newRef = { 487 | ref: 'refs/heads/new-test-branch', 488 | sha: refSpec.object.sha, 489 | }; 490 | remoteRepo.createRef(newRef, assertSuccessful(done)); 491 | })); 492 | }); 493 | 494 | it('should update commit status', function(done) { 495 | const status = { 496 | state: 'success', 497 | target_url: 'http://example.com', // eslint-disable-line camelcase 498 | description: 'Build was successful!', 499 | }; 500 | remoteRepo.getRef('heads/master', assertSuccessful(done, function(err, refSpec) { 501 | remoteRepo.updateStatus(refSpec.object.sha, status, assertSuccessful(done, 502 | function(err, updated) { 503 | expect(updated).to.have.own('state', status.state); 504 | expect(updated).to.have.own('target_url', status.target_url); 505 | expect(updated).to.have.own('description', status.description); 506 | expect(updated).to.have.own('context', 'default'); 507 | done(); 508 | })); 509 | })); 510 | }); 511 | 512 | it('should delete ref on repo', function(done) { 513 | remoteRepo.deleteRef('heads/new-test-branch', assertSuccessful(done)); 514 | }); 515 | 516 | it('should list tags on repo', function(done) { 517 | remoteRepo.listTags(assertSuccessful(done)); 518 | }); 519 | 520 | it('should list pulls on repo', function(done) { 521 | const filterOpts = { 522 | state: 'all', 523 | sort: 'updated', 524 | direction: 'desc', 525 | page: 1, 526 | per_page: 10 //eslint-disable-line 527 | }; 528 | 529 | remoteRepo.listPullRequests(filterOpts, assertSuccessful(done, function(err, pullRequests) { 530 | expect(pullRequests).to.be.an.array(); 531 | expect(pullRequests).to.have.length(1); 532 | 533 | done(); 534 | })); 535 | }); 536 | 537 | it('should get pull requests on repo', function(done) { 538 | const repo = github.getRepo('github-tools', 'github'); 539 | 540 | repo.getPullRequest(153, assertSuccessful(done, function(err, pr) { 541 | expect(pr).to.have.own('title'); 542 | expect(pr).to.have.own('body'); 543 | expect(pr).to.have.own('url'); 544 | 545 | done(); 546 | })); 547 | }); 548 | 549 | it('should delete a file on the repo', function(done) { 550 | remoteRepo.writeFile('master', fileToDelete, initialText, deleteMessage, assertSuccessful(done, function() { 551 | remoteRepo.deleteFile('master', fileToDelete, assertSuccessful(done)); 552 | })); 553 | }); 554 | 555 | it('should write author and committer to repo', function(done) { 556 | const options = { 557 | author: {name: 'Author Name', email: 'author@example.com'}, 558 | committer: {name: 'Committer Name', email: 'committer@example.com'}, 559 | }; 560 | 561 | remoteRepo.writeFile('dev', 562 | fileName, initialText, initialMessage, options, 563 | assertSuccessful(done, function(error, commit) { 564 | remoteRepo.getCommit(commit.commit.sha, assertSuccessful(done, function(err, commit2) { 565 | const author = commit2.author; 566 | const committer = commit2.committer; 567 | expect(author.name).to.be('Author Name'); 568 | expect(author.email).to.be('author@example.com'); 569 | expect(committer.name).to.be('Committer Name'); 570 | expect(committer.email).to.be('committer@example.com'); 571 | 572 | done(); 573 | })); 574 | }) 575 | ); 576 | }); 577 | 578 | it('should be able to write all the unicode', function(done) { 579 | remoteRepo.writeFile('master', unicodeFileName, unicodeText, unicodeMessage, assertSuccessful(done, 580 | function(err, commit) { 581 | expect(commit.content.name).to.be(unicodeFileName); 582 | expect(commit.commit.message).to.be(unicodeMessage); 583 | 584 | remoteRepo.getContents('master', unicodeFileName, 'raw', assertSuccessful(done, function(err, fileText) { 585 | expect(fileText).to.be(unicodeText); 586 | 587 | done(); 588 | })); 589 | })); 590 | }); 591 | 592 | it('should pass a regression test for _request (#14)', function(done) { 593 | remoteRepo.getRef('heads/master', assertSuccessful(done, function(err, refSpec) { 594 | let newRef = { 595 | ref: 'refs/heads/testing-14', 596 | sha: refSpec.object.sha, 597 | }; 598 | 599 | remoteRepo.createRef(newRef, assertSuccessful(done, function() { 600 | // Triggers GET: 601 | // https://api.github.com/repos/michael/cmake_cdt7_stalled/git/refs/heads/prose-integration 602 | remoteRepo.getRef('heads/master', assertSuccessful(done, function() { 603 | // Triggers DELETE: 604 | // https://api.github.com/repos/michael/cmake_cdt7_stalled/git/refs/heads/prose-integration 605 | remoteRepo.deleteRef('heads/testing-14', assertSuccessful(done, function(err, res, xhr) { 606 | expect(xhr.status).to.be(204); 607 | 608 | done(); 609 | })); 610 | })); 611 | })); 612 | })); 613 | }); 614 | 615 | it('should be able to write an image to the repo', function(done) { 616 | const opts = { 617 | encode: false, 618 | }; 619 | 620 | remoteRepo.writeFile('master', imageFileName, imageB64, initialMessage, opts, assertSuccessful(done, 621 | function(err, commit) { 622 | sha = commit.sha; 623 | 624 | done(); 625 | })); 626 | }); 627 | 628 | it('should be able to write a string blob to the repo', function(done) { 629 | remoteRepo.createBlob('String test', assertSuccessful(done)); 630 | }); 631 | 632 | it('should be able to write a file blob to the repo', function(done) { 633 | remoteRepo.createBlob(imageBlob, assertSuccessful(done)); 634 | }); 635 | 636 | it('should star the repo', function(done) { 637 | remoteRepo.star(assertSuccessful(done, function() { 638 | remoteRepo.isStarred(assertSuccessful(done)); 639 | })); 640 | }); 641 | 642 | it('should unstar the repo', function(done) { 643 | remoteRepo.unstar(assertSuccessful(done, function() { 644 | remoteRepo.isStarred(assertSuccessful(done, function(_, isStarred) { 645 | expect(isStarred).to.be(false); 646 | done(); 647 | })); 648 | })); 649 | }); 650 | 651 | it('should fail on broken commit', function(done) { 652 | remoteRepo.commit('broken-parent-hash', 'broken-tree-hash', initialMessage, assertFailure(done, function(err) { 653 | expect(err.response.status).to.be(422); 654 | done(); 655 | })); 656 | }); 657 | 658 | it('should succeed on proper commit', function(done) { 659 | let parentSHA = ''; 660 | let treeSHA = ''; 661 | remoteRepo.getRef('heads/master').then((ref) => { 662 | parentSHA = ref.data.object.sha; 663 | return remoteRepo.getCommit(parentSHA); 664 | }).then((commit) => { 665 | treeSHA = commit.data.tree.sha; 666 | return remoteRepo.commit(parentSHA, treeSHA, 'is this thing on?'); 667 | }).then((commit) => { 668 | expect(commit.data.author).to.have.own('name', 'github-tools-test'); 669 | done(); 670 | }); 671 | }); 672 | 673 | it('should allow commit to change author', function(done) { 674 | let parentSHA = ''; 675 | let treeSHA = ''; 676 | remoteRepo.getRef('heads/master').then((ref) => { 677 | parentSHA = ref.data.object.sha; 678 | return remoteRepo.getCommit(parentSHA); 679 | }).then((commit) => { 680 | treeSHA = commit.data.tree.sha; 681 | return remoteRepo.commit(parentSHA, treeSHA, 'Who made this commit?', { 682 | author: { 683 | name: 'Jimothy Halpert', 684 | email: 'jim@dundermifflin.com', 685 | }, 686 | }); 687 | }).then((commit) => { 688 | expect(commit.data.author).to.have.own('name', 'Jimothy Halpert'); 689 | expect(commit.data.author).to.have.own('email', 'jim@dundermifflin.com'); 690 | done(); 691 | }).catch((err) => { 692 | throw err; 693 | }); 694 | }); 695 | 696 | it('should create a release', function(done) { 697 | const releaseDef = { 698 | name: releaseName, 699 | tag_name: releaseTag, // eslint-disable-line 700 | target_commitish: sha // eslint-disable-line 701 | }; 702 | 703 | remoteRepo.createRelease(releaseDef, assertSuccessful(done, function(err, res) { 704 | releaseId = res.id; 705 | done(); 706 | })); 707 | }); 708 | 709 | it('should edit a release', function(done) { 710 | const releaseDef = { 711 | name: releaseName, 712 | body: releaseBody, 713 | }; 714 | 715 | remoteRepo.updateRelease(releaseId, releaseDef, assertSuccessful(done, function(err, release) { 716 | expect(release).to.have.own('name', releaseName); 717 | expect(release).to.have.own('body', releaseBody); 718 | 719 | done(); 720 | })); 721 | }); 722 | 723 | it('should read all releases', function(done) { 724 | remoteRepo.listReleases(assertSuccessful(done, function(err, releases) { 725 | expect(releases).to.be.an.array(); 726 | done(); 727 | })); 728 | }); 729 | 730 | it('should read a release', function(done) { 731 | remoteRepo.getRelease(releaseId, assertSuccessful(done, function(err, release) { 732 | expect(release).to.have.own('name', releaseName); 733 | 734 | done(); 735 | })); 736 | }); 737 | 738 | it('should delete a release', function(done) { 739 | remoteRepo.deleteRelease(releaseId, assertSuccessful(done)); 740 | }); 741 | 742 | it('should create a project', function(done) { 743 | remoteRepo.createProject({ 744 | name: 'test-project', 745 | body: 'body', 746 | }, assertSuccessful(done, function(err, project) { 747 | expect(project).to.own('name', 'test-project'); 748 | expect(project).to.own('body', 'body'); 749 | done(); 750 | })); 751 | }); 752 | 753 | it('should list repo projects', function(done) { 754 | remoteRepo.listProjects(assertSuccessful(done, function(err, projects) { 755 | expect(projects).to.be.an.array(); 756 | expect(projects.length).to.equal(1); 757 | done(); 758 | })); 759 | }); 760 | }); 761 | 762 | describe('deleting', function() { 763 | let remoteRepo; 764 | before(function() { 765 | remoteRepo = github.getRepo(testUser.USERNAME, testRepoName); 766 | }); 767 | 768 | // 200ms between tests so that Github has a chance to settle 769 | beforeEach(function(done) { 770 | setTimeout(done, 200); 771 | }); 772 | 773 | it('should delete the repo', function(done) { 774 | remoteRepo.deleteRepo(assertSuccessful(done, function(err, result) { 775 | expect(result).to.be(true); 776 | 777 | done(); 778 | })); 779 | }); 780 | }); 781 | }); 782 | -------------------------------------------------------------------------------- /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.js'; 6 | 7 | describe('Search', function() { 8 | this.timeout(20 * 1000); // eslint-disable-line no-invalid-this 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.js'; 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 | const org = github.getOrganization(testUser.ORGANIZATION); 44 | 45 | /* eslint-disable no-console */ 46 | // The code below add a fixed-test-repo-1 47 | let promiseRepo = new Promise((resolve) => { 48 | org 49 | .createRepo({name: 'fixed-test-repo-1'}) 50 | .then(resolve, () => { 51 | console.log('skiped fixed-test-repo-1 creation'); 52 | resolve(); 53 | }); 54 | }); 55 | 56 | // The code below add a fixed-test-repo-1 57 | let promiseTeam = org 58 | .createTeam({ 59 | name: 'fixed-test-repo-1', 60 | repo_names: [testUser.ORGANIZATION + '/fixed-test-repo-1'], // eslint-disable-line camelcase 61 | }) 62 | .then(({data: team}) => team) 63 | .catch(() => { 64 | console.log('skiped fixed-test-repo-1 creation'); 65 | // Team already exists, fetch the team 66 | return org.getTeams().then(({data: teams}) => { 67 | let team = teams 68 | .filter((team) => team.name === 'fixed-test-repo-1') 69 | .pop(); 70 | if (!team) { 71 | throw new Error('missing fixed-test-repo-1'); 72 | } 73 | return team; 74 | }); 75 | }); 76 | /* eslint-enable no-console */ 77 | 78 | return promiseRepo.then(() => { 79 | return promiseTeam 80 | .then((t) => { 81 | team = github.getTeam(t.id); 82 | return team; 83 | }) 84 | .then((team) => { 85 | let setupTeam = [ 86 | team.addMembership(altUser.USERNAME), 87 | team.addMembership(testUser.USERNAME), 88 | team.manageRepo(testUser.ORGANIZATION, 'fixed-test-repo-1'), 89 | ]; 90 | return Promise.all(setupTeam); 91 | }); 92 | }); 93 | }); 94 | 95 | it('should get membership for a given user', function() { 96 | return team.getMembership(altUser.USERNAME) 97 | .then(function({data}) { 98 | expect(data.state).to.equal('active'); 99 | expect(data.role).to.equal('member'); 100 | }); 101 | }); 102 | 103 | it('should list the users in the team', function() { 104 | return team.listMembers() 105 | .then(function({data: members}) { 106 | expect(members).to.be.an.array(); 107 | 108 | const hasTestUser = members.some((member) => member.login === testUser.USERNAME); 109 | 110 | expect(hasTestUser).to.be.true(); 111 | }); 112 | }); 113 | 114 | it('should get team repos', function() { 115 | return team.listRepos() 116 | .then(({data}) => { 117 | const hasRepo = data.some((repo) => repo.name === 'fixed-test-repo-1'); 118 | 119 | expect(hasRepo).to.be.true(); 120 | }); 121 | }); 122 | 123 | it('should get team', function() { 124 | return team.getTeam() 125 | .then(({data}) => { 126 | expect(data.name).to.equal('fixed-test-repo-1'); 127 | }); 128 | }); 129 | 130 | it('should check if team manages repo', function() { 131 | return team.isManagedRepo(testUser.ORGANIZATION, 'fixed-test-repo-1') 132 | .then((result) => { 133 | expect(result).to.be.true(); 134 | }); 135 | }); 136 | }); 137 | 138 | describe('Team', function() { // Isolate tests that need a new team per test 139 | beforeEach(function() { 140 | return createTestTeam() 141 | .then((x) => { 142 | team = x.team; 143 | name = x.name; 144 | }); 145 | }); 146 | 147 | // Test for Team deletion 148 | afterEach(function(done) { 149 | team.deleteTeam() 150 | .then(() => team.getTeam(assertFailure(done))); 151 | }); 152 | 153 | it('should update team', function() { 154 | const newName = `${name}-updated`; 155 | return team.editTeam({name: newName}) 156 | .then(function({data}) { 157 | expect(data.name).to.equal(newName); 158 | }); 159 | }); 160 | 161 | it('should add membership for a given user', function() { 162 | return team.addMembership(altUser.USERNAME) 163 | .then(({data}) => { 164 | const {state, role} = data; 165 | expect(state === 'active' || state === 'pending').to.be.true(); 166 | expect(role).to.equal('member'); 167 | }); 168 | }); 169 | 170 | it('should add membership as a maintainer for a given user', function() { 171 | return team.addMembership(altUser.USERNAME, {role: 'maintainer'}) 172 | .then(({data}) => { 173 | const {state, role} = data; 174 | expect(state === 'active' || state === 'pending').to.be.true(); 175 | expect(role).to.equal('maintainer'); 176 | }); 177 | }); 178 | 179 | it('should add/remove team management of repo', function() { 180 | return team.manageRepo(testUser.ORGANIZATION, 'fixed-test-repo-1', {permission: 'pull'}) 181 | .then((result) => { 182 | expect(result).to.be.true(); 183 | return team.unmanageRepo(testUser.ORGANIZATION, 'fixed-test-repo-1'); 184 | }) 185 | .then((result) => { 186 | expect(result).to.be.true(); 187 | }); 188 | }); 189 | }); 190 | -------------------------------------------------------------------------------- /test/user.spec.js: -------------------------------------------------------------------------------- 1 | import expect from 'must'; 2 | 3 | import Github from '../lib/GitHub'; 4 | import testUser from './fixtures/user.js'; 5 | import {assertSuccessful, assertArray} from './helpers/callbacks'; 6 | 7 | describe('User', function() { 8 | let github; 9 | let user; 10 | 11 | before(function() { 12 | github = new Github({ 13 | username: testUser.USERNAME, 14 | password: testUser.PASSWORD, 15 | auth: 'basic', 16 | }); 17 | user = github.getUser(); 18 | }); 19 | 20 | it('should get user repos', function(done) { 21 | user.listRepos(assertArray(done)); 22 | }); 23 | 24 | it('should get user repos with options', function(done) { 25 | const filterOpts = { 26 | type: 'owner', 27 | sort: 'updated', 28 | per_page: 90, // eslint-disable-line 29 | page: 10, 30 | }; 31 | 32 | user.listRepos(filterOpts, assertArray(done)); 33 | }); 34 | 35 | it('should get user orgs', function(done) { 36 | user.listOrgs(assertArray(done)); 37 | }); 38 | 39 | it('should get user followers', function(done) { 40 | user.listFollowers(assertArray(done)); 41 | }); 42 | 43 | it('should get user following list', function(done) { 44 | user.listFollowing(assertArray(done)); 45 | }); 46 | 47 | it('should get user gists', function(done) { 48 | user.listGists(assertArray(done)); 49 | }); 50 | 51 | it('should get user notifications', function(done) { 52 | user.listNotifications(assertArray(done)); 53 | }); 54 | 55 | it('should get user notifications with options', function(done) { 56 | const filterOpts = { 57 | all: true, 58 | participating: true, 59 | since: '2015-01-01T00:00:00Z', 60 | before: '2015-02-01T00:00:00Z', 61 | }; 62 | 63 | user.listNotifications(filterOpts, assertArray(done)); 64 | }); 65 | 66 | it('should get the user\'s profile', function(done) { 67 | user.getProfile(assertSuccessful(done)); 68 | }); 69 | 70 | it('should show user\'s starred repos', function(done) { 71 | user.listStarredRepos(assertArray(done)); 72 | }); 73 | 74 | it('should show user\'s starred gists', function(done) { 75 | const option = { 76 | since: '2015-01-01T00:00:00Z', 77 | }; 78 | user.listStarredGists(option, assertArray(done)); 79 | }); 80 | 81 | describe('following a user', function() { 82 | const userToFollow = 'ingalls'; 83 | 84 | before(function() { 85 | return user.unfollow(userToFollow); 86 | }); 87 | 88 | it('should follow user', function(done) { 89 | user.follow(userToFollow, assertSuccessful(done, function(err, resp) { 90 | user._request('GET', '/user/following', null, assertSuccessful(done, function(err, following) { 91 | expect((following.some((user) => user['login'] === userToFollow))).to.be.true(); 92 | done(); 93 | })); 94 | })); 95 | }); 96 | }); 97 | 98 | describe('following yourself', function() { 99 | const userToFollow = testUser.USERNAME; 100 | 101 | before(function() { 102 | return user.unfollow(userToFollow); 103 | }); 104 | 105 | it('should not list yourself as one of your followers', function(done) { 106 | user.follow(userToFollow, assertSuccessful(done, function(err, resp) { 107 | user._request('GET', '/user/following', null, assertSuccessful(done, function(err, following) { 108 | expect((following.some((user) => user['login'] === userToFollow))).to.be.false(); 109 | done(); 110 | })); 111 | })); 112 | }); 113 | }); 114 | 115 | describe('unfollowing a user', function(done) { 116 | const userToUnfollow = 'ingalls'; 117 | 118 | before(function() { 119 | return user.follow(userToUnfollow); 120 | }); 121 | 122 | it('should unfollow a user', function(done) { 123 | user.unfollow(userToUnfollow, assertSuccessful(done, function(err, resp) { 124 | user._request('GET', '/user/following', null, assertSuccessful(done, function(err, following) { 125 | expect((following.some((user) => user['login'] === userToUnfollow))).to.be.false(); 126 | done(); 127 | })); 128 | })); 129 | }); 130 | }); 131 | 132 | it('should list the email addresses of the user', function(done) { 133 | user.getEmails(assertSuccessful(done)); 134 | }); 135 | }); 136 | -------------------------------------------------------------------------------- /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 | --------------------------------------------------------------------------------