diff --git a/.gitignore b/.gitignore index 6d462a19..db3cda2f 100644 --- a/.gitignore +++ b/.gitignore @@ -4,7 +4,8 @@ docs/ dist/ coverage/ node_modules/ - +.nyc_output/ +/out/ .DS_Store npm-debug.log sauce.json diff --git a/.npmignore b/.npmignore index 15fff57d..fe969272 100644 --- a/.npmignore +++ b/.npmignore @@ -1,6 +1,7 @@ docs/ coverage/ node_modules/ - +lib/ +.nyc_output/ .DS_Store sauce.json diff --git a/.travis.yml b/.travis.yml index c00581ca..6b6bad91 100644 --- a/.travis.yml +++ b/.travis.yml @@ -1,25 +1,21 @@ sudo: false language: node_js node_js: - - '6' - - '5' - - '4' - - '0.12' - +- '11' +- '10' +- '8' cache: directories: - - node_modules + - node_modules before_install: npm install -g npm@latest before_script: - - npm run lint - # - npm run build # will need this when we do sauce testing of compiled files +- npm run lint script: - - npm test - # - npm run test-dist # test the compiled files -# after_success: -# - npm run codecov # disabled temporarialy while I work out how to generate accurate coverage of ES2015 code +- npm run test-coverage +after_success: +- npm run codecov before_deploy: - - npm run build +- npm run build deploy: provider: npm skip_cleanup: true @@ -27,4 +23,4 @@ deploy: tags: true email: clayreimann@gmail.com api_key: - secure: TZHqJ9Kh2Qf0GAVDjEOQ01Ez6rGMYHKwVLOKTbnb7nSzF7iiGNT4UwzvYawm0T9p1k7X1WOqW3l7OEbIwoKl7/9azT4BBJm7qUMRfB9Zio5cL3rKubJVz7+LEEIW4iBeDWLanhUDgy9BO2JKCt8bfp/U2tltgXtu9Fm/UFPALI8= + secure: WnLh1m02aF7NvFNILCZ8KsjPuDeSddQI87y8dwAixStr2FhQyz8FIKZN2Qj1N1Q9ZJvBETe5HWs1c9yOjTKBkD0d/eU2hlpnB9WXEFRJVDjiUuMnpAMMvuqTZwYg6kXq5N+of95PX58AYiBiV/qwsdUr/MgjEEYLt5UZgRYQRvE= diff --git a/CHANGELOG.md b/CHANGELOG.md index 925b11c1..035944d8 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,5 +1,19 @@ # Change Log +## 2.4.0 - 2016/09/16 +### Features +* add `Issue.createLabel` +* add `Repository.createKey` +* add `Repository.deleteKey` +* add `Repository.getBranch` +* add `Repository.listKeys` +* add `Repository.getKey` +* add `Repository.updatePullRequest` +* deprecate `Repository.updatePullRequst` + +### Fixes +* Request URL for deleting a hook (`Repository.deleteHook`) + ## 2.3.0 - 2016/06/17 ### Features * add `Repository.mergePullRequest` diff --git a/CODE_OF_CONDUCT.md b/CODE_OF_CONDUCT.md new file mode 100644 index 00000000..09827eb1 --- /dev/null +++ b/CODE_OF_CONDUCT.md @@ -0,0 +1,76 @@ +# Contributor Covenant Code of Conduct + +## Our Pledge + +In the interest of fostering an open and welcoming environment, we as +contributors and maintainers pledge to making participation in our project and +our community a harassment-free experience for everyone, regardless of age, body +size, disability, ethnicity, sex characteristics, gender identity and expression, +level of experience, education, socio-economic status, nationality, personal +appearance, race, religion, or sexual identity and orientation. + +## Our Standards + +Examples of behavior that contributes to creating a positive environment +include: + +* Using welcoming and inclusive language +* Being respectful of differing viewpoints and experiences +* Gracefully accepting constructive criticism +* Focusing on what is best for the community +* Showing empathy towards other community members + +Examples of unacceptable behavior by participants include: + +* The use of sexualized language or imagery and unwelcome sexual attention or + advances +* Trolling, insulting/derogatory comments, and personal or political attacks +* Public or private harassment +* Publishing others' private information, such as a physical or electronic + address, without explicit permission +* Other conduct which could reasonably be considered inappropriate in a + professional setting + +## Our Responsibilities + +Project maintainers are responsible for clarifying the standards of acceptable +behavior and are expected to take appropriate and fair corrective action in +response to any instances of unacceptable behavior. + +Project maintainers have the right and responsibility to remove, edit, or +reject comments, commits, code, wiki edits, issues, and other contributions +that are not aligned to this Code of Conduct, or to ban temporarily or +permanently any contributor for other behaviors that they deem inappropriate, +threatening, offensive, or harmful. + +## Scope + +This Code of Conduct applies both within project spaces and in public spaces +when an individual is representing the project or its community. Examples of +representing a project or community include using an official project e-mail +address, posting via an official social media account, or acting as an appointed +representative at an online or offline event. Representation of a project may be +further defined and clarified by project maintainers. + +## Enforcement + +Instances of abusive, harassing, or otherwise unacceptable behavior may be +reported by contacting the project team at . All +complaints will be reviewed and investigated and will result in a response that +is deemed necessary and appropriate to the circumstances. The project team is +obligated to maintain confidentiality with regard to the reporter of an incident. +Further details of specific enforcement policies may be posted separately. + +Project maintainers who do not follow or enforce the Code of Conduct in good +faith may face temporary or permanent repercussions as determined by other +members of the project's leadership. + +## Attribution + +This Code of Conduct is adapted from the [Contributor Covenant][homepage], version 1.4, +available at https://www.contributor-covenant.org/version/1/4/code-of-conduct.html + +[homepage]: https://www.contributor-covenant.org + +For answers to common questions about this code of conduct, see +https://www.contributor-covenant.org/faq diff --git a/README.md b/README.md index 28f61b90..5e5f527f 100644 --- a/README.md +++ b/README.md @@ -1,51 +1,23 @@ +# Maintainers wanted +[Apply within](https://github.com/github-tools/github/issues/539) + # Github.js [![Downloads per month](https://img.shields.io/npm/dm/github-api.svg?maxAge=2592000)][npm-package] [![Latest version](https://img.shields.io/npm/v/github-api.svg?maxAge=3600)][npm-package] -[![Gitter](https://img.shields.io/gitter/room/michael/github.js.svg?maxAge=2592000)][gitter] -[![Travis](https://img.shields.io/travis/michael/github.svg?maxAge=60)][travis-ci] - - -Github.js provides a minimal higher-level wrapper around Github's API. It was concieved in the context of -[Prose][prose], a content editor for GitHub. - -## [Read the docs][docs] - -## Installation -Github.js is available from `npm` or [npmcdn][npmcdn]. - -```shell -npm install github-api -``` - -```html - - - - - -``` - -## Compatibility -Github.js is tested on Node: -* 6.x -* 5.x -* 4.x -* 0.12 - -## GitHub Tools +[![Gitter](https://img.shields.io/gitter/room/github-tools/github.js.svg?maxAge=2592000)][gitter] +[![Travis](https://img.shields.io/travis/github-tools/github.svg?maxAge=60)][travis-ci] +[![Codecov](https://img.shields.io/codecov/c/github/github-tools/github.svg?maxAge=2592000)][codecov] -The team behind Github.js has created a whole organization, called [GitHub Tools](https://github.com/github-tools), -dedicated to GitHub and its API. In the near future this repository could be moved under the GitHub Tools organization -as well. In the meantime, we recommend you to take a look at other projects of the organization. +`Github.js` provides a minimal higher-level wrapper around Github's API. -## Samples +## Usage ```javascript /* Data can be retrieved from the API either using callbacks (as in versions < 1.0) - or using a new promise-based API. For now the promise-based API just returns the - raw HTTP request promise; this might change in the next version. + or using a new promise-based API. The promise-based API returns the raw Axios + request promise. */ import GitHub from 'github-api'; @@ -57,62 +29,98 @@ gist.create({ description: 'My first gist', files: { "file1.txt": { - contents: "Aren't gists great!" + content: "Aren't gists great!" } } }).then(function({data}) { // Promises! - let gistJson = data; - gist.read(function(err, gist, xhr) { - // if no error occurred then err == null - - // gistJson === httpResponse.data - - // xhr === httpResponse - }); + let createdGist = data; + return gist.read(); +}).then(function({data}) { + let retrievedGist = data; + // do interesting things }); ``` ```javascript -import GitHub from 'github-api'; +var GitHub = require('github-api'); // basic auth -const gh = new GitHub({ +var gh = new GitHub({ username: 'FOO', password: 'NotFoo' + /* also acceptable: + token: 'MY_OAUTH_TOKEN' + */ }); -const me = gh.getUser(); -me.listNotifications(function(err, notifcations) { +var me = gh.getUser(); // no user specified defaults to the user for whom credentials were provided +me.listNotifications(function(err, notifications) { // do some stuff }); -const clayreimann = gh.getUser('clayreimann'); -clayreimann.getStarredRepos() - .then(function({data: reposJson}) { - // do stuff with reposJson - }); +var clayreimann = gh.getUser('clayreimann'); +clayreimann.listStarredRepos(function(err, repos) { + // look at all the starred repos! +}); ``` -```javascript -var GitHub = require('github-api'); +## API Documentation -// token auth -var gh = new GitHub({ - token: 'MY_OAUTH_TOKEN' -}); +[API documentation][docs] is hosted on github pages, and is generated from JSDoc; any contributions +should include updated JSDoc. + +## Installation +`Github.js` is available from `npm` or [unpkg][unpkg]. -var yahoo = gh.getOrganization('yahoo'); -yahoo.getRepos(function(err, repos) { - // look at all the repos! -}) +```shell +npm install github-api ``` -[codecov]: https://codecov.io/github/michael/github?branch=master -[docs]: http://michael.github.io/github/ -[gitter]: https://gitter.im/michael/github +```html + + + + + +``` + +## Compatibility +`Github.js` is tested on node's LTS and current versions. + +[codecov]: https://codecov.io/github/github-tools/github?branch=master +[docs]: http://github-tools.github.io/github/ +[gitter]: https://gitter.im/github-tools/github [npm-package]: https://www.npmjs.com/package/github-api/ -[npmcdn]: https://npmcdn.com/github-api/ -[prose]: http://prose.io -[travis-ci]: https://travis-ci.org/michael/github -[xhr-link]: http://blogs.msdn.com/b/ieinternals/archive/2010/05/13/xdomainrequest-restrictions-limitations-and-workarounds.aspx +[unpkg]: https://unpkg.com/github-api/ +[travis-ci]: https://travis-ci.org/github-tools/github + +## Contributing + +We welcome contributions of all types! This section will guide you through setting up your development environment. + +### Setup + +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). +2. Fork this repo to your GitHub account. +3. Clone the fork to your development machine (`git clone https://github.com/{YOUR_USERNAME}/github`). +4. From the root of the cloned repo, run `npm install`. +5. Email jaredrewerts@gmail.com with the subject **GitHub API - Personal Access Token Request** + +A personal access token for our test user, @github-tools-test, will be generated for you. + +6. Set the environment variable `GHTOOLS_USER` to `github-tools-test`. + +`export GHTOOLS_USER=github-tools-test` + +7. Set the environment variable `GHTOOLS_PASSWORD` to the personal access token that was generated for you. + +`export GHTOOLS_PASSWORD={YOUR_PAT}` + +**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. + +### Tests + +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. + +To run the test suite, run `npm run test`. diff --git a/gulpfile.babel.js b/gulpfile.babel.js index c3d669f5..9965f7aa 100644 --- a/gulpfile.babel.js +++ b/gulpfile.babel.js @@ -13,7 +13,7 @@ import uglify from 'gulp-uglify'; const ALL_SOURCES = [ '*.js', 'lib/*.js', - 'test/*.js' + 'test/*.js', ]; gulp.task('lint', function() { @@ -33,13 +33,13 @@ gulp.task('build', [ 'build:external:min', 'build:bundled:debug', 'build:external:debug', - 'build:components' + 'build:components', ]); const bundledConfig = { debug: true, entries: 'lib/GitHub.js', - standalone: 'GitHub' + standalone: 'GitHub', }; const externalConfig = { debug: true, @@ -50,9 +50,9 @@ const externalConfig = { 'js-base64', 'es6-promise', 'debug', - 'utf8' + 'utf8', ], - bundleExternal: false + bundleExternal: false, }; gulp.task('build:bundled:min', function() { return buildBundle(bundledConfig, '.bundle.min.js', true); @@ -82,7 +82,7 @@ function buildBundle(options, extname, minify) { .pipe(source('GitHub.js')) .pipe(buffer()) .pipe(sourcemaps.init({ - loadMaps: true + loadMaps: true, })); if (minify) { diff --git a/lib/Gist.js b/lib/Gist.js index 14b71322..60f18cf5 100644 --- a/lib/Gist.js +++ b/lib/Gist.js @@ -108,6 +108,27 @@ class Gist extends Requestable { return this._request204or404(`/gists/${this.__id}/star`, null, cb); } + /** + * List the gist's commits + * @see https://developer.github.com/v3/gists/#list-gist-commits + * @param {Requestable.callback} [cb] - will receive the array of commits + * @return {Promise} - the Promise for the http request + */ + listCommits(cb) { + return this._requestAllPages(`/gists/${this.__id}/commits`, null, cb); + } + + /** + * Fetch one of the gist's revision. + * @see https://developer.github.com/v3/gists/#get-a-specific-revision-of-a-gist + * @param {string} revision - the id of the revision + * @param {Requestable.callback} [cb] - will receive the revision + * @return {Promise} - the Promise for the http request + */ + getRevision(revision, cb) { + return this._request('GET', `/gists/${this.__id}/${revision}`, null, cb); + } + /** * List the gist's comments * @see https://developer.github.com/v3/gists/comments/#list-comments-on-a-gist diff --git a/lib/GitHub.js b/lib/GitHub.js index 944fc868..7c3ac24c 100644 --- a/lib/GitHub.js +++ b/lib/GitHub.js @@ -15,6 +15,7 @@ import Repository from './Repository'; import Organization from './Organization'; import Team from './Team'; import Markdown from './Markdown'; +import Project from './Project'; /** * GitHub encapsulates the functionality to create various API wrapper objects. @@ -33,7 +34,7 @@ class GitHub { /** * Create a new Gist wrapper - * @param {number} [id] - the id for the gist, leave undefined when creating a new gist + * @param {string} [id] - the id for the gist, leave undefined when creating a new gist * @return {Gist} */ getGist(id) { @@ -70,7 +71,7 @@ class GitHub { /** * Create a new Repository wrapper - * @param {string} user - the user who owns the respository + * @param {string} user - the user who owns the repository * @param {string} repo - the name of the repository * @return {Repository} */ @@ -80,7 +81,7 @@ class GitHub { /** * Create a new Issue wrapper - * @param {string} user - the user who owns the respository + * @param {string} user - the user who owns the repository * @param {string} repo - the name of the repository * @return {Issue} */ @@ -113,6 +114,15 @@ class GitHub { return new Markdown(this.__auth, this.__apiBase); } + /** + * Create a new Project wrapper + * @param {string} id - the id of the project + * @return {Project} + */ + getProject(id) { + return new Project(id, this.__auth, this.__apiBase); + } + /** * Computes the full repository name * @param {string} user - the username (or the full name) diff --git a/lib/Issue.js b/lib/Issue.js index d5c03d4a..c0151b5f 100644 --- a/lib/Issue.js +++ b/lib/Issue.js @@ -150,7 +150,7 @@ class Issue extends Requestable { * Get a milestone * @see https://developer.github.com/v3/issues/milestones/#get-a-single-milestone * @param {string} milestone - the id of the milestone to fetch - * @param {Requestable.callback} [cb] - will receive the array of milestones + * @param {Requestable.callback} [cb] - will receive the milestone * @return {Promise} - the promise for the http request */ getMilestone(milestone, cb) { @@ -161,7 +161,7 @@ class Issue extends Requestable { * Create a new milestone * @see https://developer.github.com/v3/issues/milestones/#create-a-milestone * @param {Object} milestoneData - the milestone definition - * @param {Requestable.callback} [cb] - will receive the array of milestones + * @param {Requestable.callback} [cb] - will receive the milestone * @return {Promise} - the promise for the http request */ createMilestone(milestoneData, cb) { @@ -173,7 +173,7 @@ class Issue extends Requestable { * @see https://developer.github.com/v3/issues/milestones/#update-a-milestone * @param {string} milestone - the id of the milestone to edit * @param {Object} milestoneData - the updates to make to the milestone - * @param {Requestable.callback} [cb] - will receive the array of milestones + * @param {Requestable.callback} [cb] - will receive the updated milestone * @return {Promise} - the promise for the http request */ editMilestone(milestone, milestoneData, cb) { @@ -184,12 +184,68 @@ class Issue extends Requestable { * Delete a milestone (this is distinct from closing a milestone) * @see https://developer.github.com/v3/issues/milestones/#delete-a-milestone * @param {string} milestone - the id of the milestone to delete - * @param {Requestable.callback} [cb] - will receive the array of milestones + * @param {Requestable.callback} [cb] - will receive the status * @return {Promise} - the promise for the http request */ deleteMilestone(milestone, cb) { return this._request('DELETE', `/repos/${this.__repository}/milestones/${milestone}`, null, cb); } + + /** + * Create a new label + * @see https://developer.github.com/v3/issues/labels/#create-a-label + * @param {Object} labelData - the label definition + * @param {Requestable.callback} [cb] - will receive the object representing the label + * @return {Promise} - the promise for the http request + */ + createLabel(labelData, cb) { + return this._request('POST', `/repos/${this.__repository}/labels`, labelData, cb); + } + + /** + * List the labels for the repository + * @see https://developer.github.com/v3/issues/labels/#list-all-labels-for-this-repository + * @param {Object} options - filtering options + * @param {Requestable.callback} [cb] - will receive the array of labels + * @return {Promise} - the promise for the http request + */ + listLabels(options, cb) { + return this._request('GET', `/repos/${this.__repository}/labels`, options, cb); + } + + /** + * Get a label + * @see https://developer.github.com/v3/issues/labels/#get-a-single-label + * @param {string} label - the name of the label to fetch + * @param {Requestable.callback} [cb] - will receive the label + * @return {Promise} - the promise for the http request + */ + getLabel(label, cb) { + return this._request('GET', `/repos/${this.__repository}/labels/${label}`, null, cb); + } + + /** + * Edit a label + * @see https://developer.github.com/v3/issues/labels/#update-a-label + * @param {string} label - the name of the label to edit + * @param {Object} labelData - the updates to make to the label + * @param {Requestable.callback} [cb] - will receive the updated label + * @return {Promise} - the promise for the http request + */ + editLabel(label, labelData, cb) { + return this._request('PATCH', `/repos/${this.__repository}/labels/${label}`, labelData, cb); + } + + /** + * Delete a label + * @see https://developer.github.com/v3/issues/labels/#delete-a-label + * @param {string} label - the name of the label to delete + * @param {Requestable.callback} [cb] - will receive the status + * @return {Promise} - the promise for the http request + */ + deleteLabel(label, cb) { + return this._request('DELETE', `/repos/${this.__repository}/labels/${label}`, null, cb); + } } module.exports = Issue; diff --git a/lib/Markdown.js b/lib/Markdown.js index cb84851d..ebfbb512 100644 --- a/lib/Markdown.js +++ b/lib/Markdown.js @@ -8,11 +8,11 @@ import Requestable from './Requestable'; /** - * RateLimit allows users to query their rate-limit status + * Renders html from Markdown text */ class Markdown extends Requestable { /** - * construct a RateLimit + * construct a Markdown * @param {Requestable.auth} auth - the credentials to authenticate to GitHub * @param {string} [apiBase] - the base Github API URL * @return {Promise} - the promise for the http request @@ -32,7 +32,7 @@ class Markdown extends Requestable { * @return {Promise} - the promise for the http request */ render(options, cb) { - return this._request('POST', '/markdown', options, cb); + return this._request('POST', '/markdown', options, cb, true); } } diff --git a/lib/Organization.js b/lib/Organization.js index 78354a8c..0a8177b4 100644 --- a/lib/Organization.js +++ b/lib/Organization.js @@ -93,6 +93,29 @@ class Organization extends Requestable { createTeam(options, cb) { return this._request('POST', `/orgs/${this.__name}/teams`, options, cb); } + + /** + * Get information about all projects + * @see https://developer.github.com/v3/projects/#list-organization-projects + * @param {Requestable.callback} [cb] - will receive the list of projects + * @return {Promise} - the promise for the http request + */ + listProjects(cb) { + return this._requestAllPages(`/orgs/${this.__name}/projects`, {AcceptHeader: 'inertia-preview'}, cb); + } + + /** + * Create a new project + * @see https://developer.github.com/v3/repos/projects/#create-a-project + * @param {Object} options - the description of the project + * @param {Requestable.callback} cb - will receive the newly created project + * @return {Promise} - the promise for the http request + */ + createProject(options, cb) { + options = options || {}; + options.AcceptHeader = 'inertia-preview'; + return this._request('POST', `/orgs/${this.__name}/projects`, options, cb); + } } module.exports = Organization; diff --git a/lib/Project.js b/lib/Project.js new file mode 100644 index 00000000..ab31a078 --- /dev/null +++ b/lib/Project.js @@ -0,0 +1,236 @@ +/** + * @file + * @copyright 2013 Michael Aufreiter (Development Seed) and 2016 Yahoo Inc. + * @license Licensed under {@link https://spdx.org/licenses/BSD-3-Clause-Clear.html BSD-3-Clause-Clear}. + * Github.js is freely distributable. + */ + +import Requestable from './Requestable'; + +/** + * Project encapsulates the functionality to create, query, and modify cards and columns. + */ +class Project extends Requestable { + /** + * Create a Project. + * @param {string} id - the id of the project + * @param {Requestable.auth} [auth] - information required to authenticate to Github + * @param {string} [apiBase=https://api.github.com] - the base Github API URL + */ + constructor(id, auth, apiBase) { + super(auth, apiBase, 'inertia-preview'); + this.__id = id; + } + + /** + * Get information about a project + * @see https://developer.github.com/v3/projects/#get-a-project + * @param {Requestable.callback} cb - will receive the project information + * @return {Promise} - the promise for the http request + */ + getProject(cb) { + return this._request('GET', `/projects/${this.__id}`, null, cb); + } + + /** + * Edit a project + * @see https://developer.github.com/v3/projects/#update-a-project + * @param {Object} options - the description of the project + * @param {Requestable.callback} cb - will receive the modified project + * @return {Promise} - the promise for the http request + */ + updateProject(options, cb) { + return this._request('PATCH', `/projects/${this.__id}`, options, cb); + } + + /** + * Delete a project + * @see https://developer.github.com/v3/projects/#delete-a-project + * @param {Requestable.callback} cb - will receive true if the operation is successful + * @return {Promise} - the promise for the http request + */ + deleteProject(cb) { + return this._request('DELETE', `/projects/${this.__id}`, null, cb); + } + + /** + * Get information about all columns of a project + * @see https://developer.github.com/v3/projects/columns/#list-project-columns + * @param {Requestable.callback} [cb] - will receive the list of columns + * @return {Promise} - the promise for the http request + */ + listProjectColumns(cb) { + return this._requestAllPages(`/projects/${this.__id}/columns`, null, cb); + } + + /** + * Get information about a column + * @see https://developer.github.com/v3/projects/columns/#get-a-project-column + * @param {string} colId - the id of the column + * @param {Requestable.callback} cb - will receive the column information + * @return {Promise} - the promise for the http request + */ + getProjectColumn(colId, cb) { + return this._request('GET', `/projects/columns/${colId}`, null, cb); + } + + /** + * Create a new column + * @see https://developer.github.com/v3/projects/columns/#create-a-project-column + * @param {Object} options - the description of the column + * @param {Requestable.callback} cb - will receive the newly created column + * @return {Promise} - the promise for the http request + */ + createProjectColumn(options, cb) { + return this._request('POST', `/projects/${this.__id}/columns`, options, cb); + } + + /** + * Edit a column + * @see https://developer.github.com/v3/projects/columns/#update-a-project-column + * @param {string} colId - the column id + * @param {Object} options - the description of the column + * @param {Requestable.callback} cb - will receive the modified column + * @return {Promise} - the promise for the http request + */ + updateProjectColumn(colId, options, cb) { + return this._request('PATCH', `/projects/columns/${colId}`, options, cb); + } + + /** + * Delete a column + * @see https://developer.github.com/v3/projects/columns/#delete-a-project-column + * @param {string} colId - the column to be deleted + * @param {Requestable.callback} cb - will receive true if the operation is successful + * @return {Promise} - the promise for the http request + */ + deleteProjectColumn(colId, cb) { + return this._request('DELETE', `/projects/columns/${colId}`, null, cb); + } + + /** + * Move a column + * @see https://developer.github.com/v3/projects/columns/#move-a-project-column + * @param {string} colId - the column to be moved + * @param {string} position - can be one of first, last, or after:, + * where is the id value of a column in the same project. + * @param {Requestable.callback} cb - will receive true if the operation is successful + * @return {Promise} - the promise for the http request + */ + moveProjectColumn(colId, position, cb) { + return this._request( + 'POST', + `/projects/columns/${colId}/moves`, + {position: position}, + cb + ); + } + + /** + * Get information about all cards of a project + * @see https://developer.github.com/v3/projects/cards/#list-project-cards + * @param {Requestable.callback} [cb] - will receive the list of cards + * @return {Promise} - the promise for the http request + */ + listProjectCards(cb) { + return this.listProjectColumns() + .then(({data}) => { + return Promise.all(data.map((column) => { + return this._requestAllPages(`/projects/columns/${column.id}/cards`, null); + })); + }).then((cardsInColumns) => { + const cards = cardsInColumns.reduce((prev, {data}) => { + prev.push(...data); + return prev; + }, []); + if (cb) { + cb(null, cards); + } + return cards; + }).catch((err) => { + if (cb) { + cb(err); + return; + } + throw err; + }); + } + + /** + * Get information about all cards of a column + * @see https://developer.github.com/v3/projects/cards/#list-project-cards + * @param {string} colId - the id of the column + * @param {Requestable.callback} [cb] - will receive the list of cards + * @return {Promise} - the promise for the http request + */ + listColumnCards(colId, cb) { + return this._requestAllPages(`/projects/columns/${colId}/cards`, null, cb); + } + + /** + * Get information about a card + * @see https://developer.github.com/v3/projects/cards/#get-a-project-card + * @param {string} cardId - the id of the card + * @param {Requestable.callback} cb - will receive the card information + * @return {Promise} - the promise for the http request + */ + getProjectCard(cardId, cb) { + return this._request('GET', `/projects/columns/cards/${cardId}`, null, cb); + } + + /** + * Create a new card + * @see https://developer.github.com/v3/projects/cards/#create-a-project-card + * @param {string} colId - the column id + * @param {Object} options - the description of the card + * @param {Requestable.callback} cb - will receive the newly created card + * @return {Promise} - the promise for the http request + */ + createProjectCard(colId, options, cb) { + return this._request('POST', `/projects/columns/${colId}/cards`, options, cb); + } + + /** + * Edit a card + * @see https://developer.github.com/v3/projects/cards/#update-a-project-card + * @param {string} cardId - the card id + * @param {Object} options - the description of the card + * @param {Requestable.callback} cb - will receive the modified card + * @return {Promise} - the promise for the http request + */ + updateProjectCard(cardId, options, cb) { + return this._request('PATCH', `/projects/columns/cards/${cardId}`, options, cb); + } + + /** + * Delete a card + * @see https://developer.github.com/v3/projects/cards/#delete-a-project-card + * @param {string} cardId - the card to be deleted + * @param {Requestable.callback} cb - will receive true if the operation is successful + * @return {Promise} - the promise for the http request + */ + deleteProjectCard(cardId, cb) { + return this._request('DELETE', `/projects/columns/cards/${cardId}`, null, cb); + } + + /** + * Move a card + * @see https://developer.github.com/v3/projects/cards/#move-a-project-card + * @param {string} cardId - the card to be moved + * @param {string} position - can be one of top, bottom, or after:, + * where is the id value of a card in the same project. + * @param {string} colId - the id value of a column in the same project. + * @param {Requestable.callback} cb - will receive true if the operation is successful + * @return {Promise} - the promise for the http request + */ + moveProjectCard(cardId, position, colId, cb) { + return this._request( + 'POST', + `/projects/columns/cards/${cardId}/moves`, + {position: position, column_id: colId}, // eslint-disable-line camelcase + cb + ); + } +} + +module.exports = Project; diff --git a/lib/Repository.js b/lib/Repository.js index de77ae48..74452dde 100644 --- a/lib/Repository.js +++ b/lib/Repository.js @@ -8,13 +8,13 @@ import Requestable from './Requestable'; import Utf8 from 'utf8'; import { - Base64 + Base64, } from 'js-base64'; import debug from 'debug'; const log = debug('github:repository'); /** - * Respository encapsulates the functionality to create, query, and modify files. + * Repository encapsulates the functionality to create, query, and modify files. */ class Repository extends Requestable { /** @@ -28,7 +28,7 @@ class Repository extends Requestable { this.__fullname = fullname; this.__currentTree = { branch: null, - sha: null + sha: null, }; } @@ -152,6 +152,17 @@ class Repository extends Requestable { return this._request('GET', `/repos/${this.__fullname}/git/blobs/${sha}`, null, cb, 'raw'); } + /** + * Get a single branch + * @see https://developer.github.com/v3/repos/branches/#get-branch + * @param {string} branch - the name of the branch to fetch + * @param {Requestable.callback} cb - will receive the branch from the API + * @returns {Promise} - the promise for the http request + */ + getBranch(branch, cb) { + return this._request('GET', `/repos/${this.__fullname}/branches/${branch}`, null, cb); + } + /** * Get a commit from the repository * @see https://developer.github.com/v3/repos/commits/#get-a-single-commit @@ -177,13 +188,33 @@ class Repository extends Requestable { */ listCommits(options, cb) { options = options || {}; - + if (typeof options === 'function') { + cb = options; + options = {}; + } options.since = this._dateToISO(options.since); options.until = this._dateToISO(options.until); return this._request('GET', `/repos/${this.__fullname}/commits`, options, cb); } + /** + * List the commits on a pull request + * @see https://developer.github.com/v3/repos/commits/#list-commits-on-a-repository + * @param {number|string} number - the number of the pull request to list the commits + * @param {Object} [options] - the filtering options for commits + * @param {Requestable.callback} [cb] - will receive the commits information + * @return {Promise} - the promise for the http request + */ + listCommitsOnPR(number, options, cb) { + options = options || {}; + if (typeof options === 'function') { + cb = options; + options = {}; + } + return this._request('GET', `/repos/${this.__fullname}/pulls/${number}/commits`, options, cb); + } + /** * Gets a single commit information for a repository * @see https://developer.github.com/v3/repos/commits/#get-a-single-commit @@ -220,6 +251,17 @@ class Repository extends Requestable { return this._request('GET', `/repos/${this.__fullname}/commits/${sha}/statuses`, null, cb); } + /** + * Get the combined view of commit statuses for a particular sha, branch, or tag + * @see https://developer.github.com/v3/repos/statuses/#get-the-combined-status-for-a-specific-ref + * @param {string} sha - the sha, branch, or tag to get the combined status for + * @param {Requestable.callback} cb - will receive the combined status + * @returns {Promise} - the promise for the http request + */ + getCombinedStatus(sha, cb) { + return this._request('GET', `/repos/${this.__fullname}/commits/${sha}/status`, null, cb); + } + /** * Get a description of a git tree * @see https://developer.github.com/v3/git/trees/#get-a-tree @@ -255,21 +297,21 @@ class Repository extends Requestable { log('contet is a string'); return { content: Utf8.encode(content), - encoding: 'utf-8' + encoding: 'utf-8', }; } else if (typeof Buffer !== 'undefined' && content instanceof Buffer) { log('We appear to be in Node'); return { content: content.toString('base64'), - encoding: 'base64' + encoding: 'base64', }; } else if (typeof Blob !== 'undefined' && content instanceof Blob) { log('We appear to be in the browser'); return { content: Base64.encode(content), - encoding: 'base64' + encoding: 'base64', }; } else { // eslint-disable-line @@ -295,8 +337,8 @@ class Repository extends Requestable { path: path, sha: blobSHA, mode: '100644', - type: 'blob' - }] + type: 'blob', + }], }; return this._request('POST', `/repos/${this.__fullname}/git/trees`, newTree, cb); @@ -313,7 +355,7 @@ class Repository extends Requestable { createTree(tree, baseSHA, cb) { return this._request('POST', `/repos/${this.__fullname}/git/trees`, { tree, - base_tree: baseSHA // eslint-disable-line + base_tree: baseSHA, // eslint-disable-line camelcase }, cb); } @@ -323,16 +365,26 @@ class Repository extends Requestable { * @param {string} parent - the SHA of the parent commit * @param {string} tree - the SHA of the tree for this commit * @param {string} message - the commit message + * @param {Object} [options] - commit options + * @param {Object} [options.author] - the author of the commit + * @param {Object} [options.commiter] - the committer * @param {Requestable.callback} cb - will receive the commit that is created * @return {Promise} - the promise for the http request */ - commit(parent, tree, message, cb) { + commit(parent, tree, message, options, cb) { + if (typeof options === 'function') { + cb = options; + options = {}; + } + let data = { message, tree, - parents: [parent] + parents: [parent], }; + data = Object.assign({}, options, data); + return this._request('POST', `/repos/${this.__fullname}/git/commits`, data, cb) .then((response) => { this.__currentTree.sha = response.data.sha; // Update latest commit @@ -352,11 +404,46 @@ class Repository extends Requestable { updateHead(ref, commitSHA, force, cb) { return this._request('PATCH', `/repos/${this.__fullname}/git/refs/${ref}`, { sha: commitSHA, - force: force + force: force, }, cb); } /** + * Update commit status + * @see https://developer.github.com/v3/repos/statuses/ + * @param {string} commitSHA - the SHA of the commit that should be updated + * @param {object} options - Commit status parameters + * @param {string} options.state - The state of the status. Can be one of: pending, success, error, or failure. + * @param {string} [options.target_url] - The target URL to associate with this status. + * @param {string} [options.description] - A short description of the status. + * @param {string} [options.context] - A string label to differentiate this status among CI systems. + * @param {Requestable.callback} cb - will receive the updated commit back + * @return {Promise} - the promise for the http request + */ + updateStatus(commitSHA, options, cb) { + return this._request('POST', `/repos/${this.__fullname}/statuses/${commitSHA}`, options, cb); + } + + /** + * Update repository information + * @see https://developer.github.com/v3/repos/#edit + * @param {object} options - New parameters that will be set to the repository + * @param {string} options.name - Name of the repository + * @param {string} [options.description] - A short description of the repository + * @param {string} [options.homepage] - A URL with more information about the repository + * @param {boolean} [options.private] - Either true to make the repository private, or false to make it public. + * @param {boolean} [options.has_issues] - Either true to enable issues for this repository, false to disable them. + * @param {boolean} [options.has_wiki] - Either true to enable the wiki for this repository, false to disable it. + * @param {boolean} [options.has_downloads] - Either true to enable downloads, false to disable them. + * @param {string} [options.default_branch] - Updates the default branch for this repository. + * @param {Requestable.callback} cb - will receive the updated repository back + * @return {Promise} - the promise for the http request + */ + updateRepository(options, cb) { + return this._request('PATCH', `/repos/${this.__fullname}`, options, cb); + } + + /** * Get information about the repository * @see https://developer.github.com/v3/repos/#get * @param {Requestable.callback} cb - will receive the information about the repository @@ -373,6 +460,16 @@ class Repository extends Requestable { * @return {Promise} - the promise for the http request */ getContributors(cb) { + return this._request('GET', `/repos/${this.__fullname}/contributors`, null, cb); + } + + /** + * List the contributor stats to the repository + * @see https://developer.github.com/v3/repos/#list-contributors + * @param {Requestable.callback} cb - will receive the list of contributors + * @return {Promise} - the promise for the http request + */ + getContributorStats(cb) { return this._request('GET', `/repos/${this.__fullname}/stats/contributors`, null, cb); } @@ -410,7 +507,7 @@ class Repository extends Requestable { getContents(ref, path, raw, cb) { path = path ? `${encodeURI(path)}` : ''; return this._request('GET', `/repos/${this.__fullname}/contents/${path}`, { - ref + ref, }, cb, raw); } @@ -424,7 +521,7 @@ class Repository extends Requestable { */ getReadme(ref, raw, cb) { return this._request('GET', `/repos/${this.__fullname}/readme`, { - ref + ref, }, cb, raw); } @@ -438,6 +535,18 @@ class Repository extends Requestable { return this._request('POST', `/repos/${this.__fullname}/forks`, null, cb); } + /** + * Fork a repository to an organization + * @see https://developer.github.com/v3/repos/forks/#create-a-fork + * @param {String} org - organization where you'd like to create the fork. + * @param {Requestable.callback} cb - will receive the information about the newly created fork + * @return {Promise} - the promise for the http request + * + */ + forkToOrg(org, cb) { + return this._request('POST', `/repos/${this.__fullname}/forks?organization=${org}`, null, cb); + } + /** * List a repository's forks * @see https://developer.github.com/v3/repos/forks/#list-forks @@ -467,7 +576,7 @@ class Repository extends Requestable { let sha = response.data.object.sha; return this.createRef({ sha, - ref: `refs/heads/${newBranch}` + ref: `refs/heads/${newBranch}`, }, cb); }); } @@ -491,7 +600,7 @@ class Repository extends Requestable { * @param {Requestable.callback} [cb] - will receive the pull request information * @return {Promise} - the promise for the http request */ - updatePullRequst(number, options, cb) { + updatePullRequest(number, options, cb) { return this._request('PATCH', `/repos/${this.__fullname}/pulls/${number}`, options, cb); } @@ -547,7 +656,50 @@ class Repository extends Requestable { * @return {Promise} - the promise for the http request */ deleteHook(id, cb) { - return this._request('DELETE', `${this.__repoPath}/hooks/${id}`, null, cb); + return this._request('DELETE', `/repos/${this.__fullname}/hooks/${id}`, null, cb); + } + + /** + * List the deploy keys for the repository + * @see https://developer.github.com/v3/repos/keys/#list-deploy-keys + * @param {Requestable.callback} cb - will receive the list of deploy keys + * @return {Promise} - the promise for the http request + */ + listKeys(cb) { + return this._request('GET', `/repos/${this.__fullname}/keys`, null, cb); + } + + /** + * Get a deploy key for the repository + * @see https://developer.github.com/v3/repos/keys/#get-a-deploy-key + * @param {number} id - the id of the deploy key + * @param {Requestable.callback} cb - will receive the details of the deploy key + * @return {Promise} - the promise for the http request + */ + getKey(id, cb) { + return this._request('GET', `/repos/${this.__fullname}/keys/${id}`, null, cb); + } + + /** + * Add a new deploy key to the repository + * @see https://developer.github.com/v3/repos/keys/#add-a-new-deploy-key + * @param {Object} options - the configuration describing the new deploy key + * @param {Requestable.callback} cb - will receive the new deploy key + * @return {Promise} - the promise for the http request + */ + createKey(options, cb) { + return this._request('POST', `/repos/${this.__fullname}/keys`, options, cb); + } + + /** + * Delete a deploy key + * @see https://developer.github.com/v3/repos/keys/#remove-a-deploy-key + * @param {number} id - the id of the deploy key to be deleted + * @param {Requestable.callback} cb - will receive true if the call is successful + * @return {Promise} - the promise for the http request + */ + deleteKey(id, cb) { + return this._request('DELETE', `/repos/${this.__fullname}/keys/${id}`, null, cb); } /** @@ -564,7 +716,7 @@ class Repository extends Requestable { const deleteCommit = { message: `Delete the file at '${path}'`, sha: response.data.sha, - branch + branch, }; return this._request('DELETE', `/repos/${this.__fullname}/contents/${path}`, deleteCommit, cb); }); @@ -614,6 +766,7 @@ class Repository extends Requestable { * @return {Promise} - the promise for the http request */ writeFile(branch, path, content, message, options, cb) { + options = options || {}; if (typeof options === 'function') { cb = options; options = {}; @@ -625,7 +778,7 @@ class Repository extends Requestable { message, author: options.author, committer: options.committer, - content: shouldEncode ? Base64.encode(content) : content + content: shouldEncode ? Base64.encode(content) : content, }; return this.getSha(branch, filePath) @@ -704,7 +857,7 @@ class Repository extends Requestable { /** * Get information about a release * @see https://developer.github.com/v3/repos/releases/#get-a-single-release - * @param {strign} id - the id of the release + * @param {string} id - the id of the release * @param {Requestable.callback} cb - will receive the release information * @return {Promise} - the promise for the http request */ @@ -734,6 +887,30 @@ class Repository extends Requestable { mergePullRequest(number, options, cb) { return this._request('PUT', `/repos/${this.__fullname}/pulls/${number}/merge`, options, cb); } + + /** + * Get information about all projects + * @see https://developer.github.com/v3/projects/#list-repository-projects + * @param {Requestable.callback} [cb] - will receive the list of projects + * @return {Promise} - the promise for the http request + */ + listProjects(cb) { + return this._requestAllPages(`/repos/${this.__fullname}/projects`, {AcceptHeader: 'inertia-preview'}, cb); + } + + /** + * Create a new project + * @see https://developer.github.com/v3/projects/#create-a-repository-project + * @param {Object} options - the description of the project + * @param {Requestable.callback} cb - will receive the newly created project + * @return {Promise} - the promise for the http request + */ + createProject(options, cb) { + options = options || {}; + options.AcceptHeader = 'inertia-preview'; + return this._request('POST', `/repos/${this.__fullname}/projects`, options, cb); + } + } module.exports = Repository; diff --git a/lib/Requestable.js b/lib/Requestable.js index fa26d9d6..4d6e8d9d 100644 --- a/lib/Requestable.js +++ b/lib/Requestable.js @@ -8,14 +8,9 @@ import axios from 'axios'; import debug from 'debug'; import {Base64} from 'js-base64'; -import {polyfill} from 'es6-promise'; const log = debug('github:request'); -if (typeof Promise === 'undefined') { - polyfill(); -} - /** * The error structure returned when a network call fails */ @@ -30,7 +25,7 @@ class ResponseError extends Error { super(message); this.path = path; this.request = response.config; - this.response = response; + this.response = (response || {}).response || response; this.status = response.status; } } @@ -51,14 +46,16 @@ class Requestable { * @param {Requestable.auth} [auth] - the credentials to authenticate to Github. If auth is * not provided request will be made unauthenticated * @param {string} [apiBase=https://api.github.com] - the base Github API URL + * @param {string} [AcceptHeader=v3] - the accept header for the requests */ - constructor(auth, apiBase) { + constructor(auth, apiBase, AcceptHeader) { this.__apiBase = apiBase || 'https://api.github.com'; this.__auth = { token: auth.token, username: auth.username, - password: auth.password + password: auth.password, }; + this.__AcceptHeader = AcceptHeader || 'v3'; if (auth.token) { this.__authorizationHeader = 'token ' + auth.token; @@ -88,14 +85,20 @@ class Requestable { * Compute the headers required for an API request. * @private * @param {boolean} raw - if the request should be treated as JSON or as a raw request + * @param {string} AcceptHeader - the accept header for the request * @return {Object} - the headers to use in the request */ - __getRequestHeaders(raw) { + __getRequestHeaders(raw, AcceptHeader) { let headers = { - 'Accept': raw ? 'application/vnd.github.v3.raw+json' : 'application/vnd.github.v3+json', - 'Content-Type': 'application/json;charset=UTF-8' + 'Content-Type': 'application/json;charset=UTF-8', + 'Accept': 'application/vnd.github.' + (AcceptHeader || this.__AcceptHeader), }; + if (raw) { + headers.Accept += '.raw'; + } + headers.Accept += '+json'; + if (this.__authorizationHeader) { headers.Authorization = this.__authorizationHeader; } @@ -121,7 +124,7 @@ class Requestable { /** * if a `Date` is passed to this function it will be converted to an ISO string - * @param {*} date - the object to attempt to cooerce into an ISO date string + * @param {*} date - the object to attempt to coerce into an ISO date string * @return {string} - the ISO representation of `date` or whatever was passed in if it was not a date */ _dateToISO(date) { @@ -152,7 +155,13 @@ class Requestable { */ _request(method, path, data, cb, raw) { const url = this.__getURL(path); - const headers = this.__getRequestHeaders(raw); + + const AcceptHeader = (data || {}).AcceptHeader; + if (AcceptHeader) { + delete data.AcceptHeader; + } + const headers = this.__getRequestHeaders(raw, AcceptHeader); + let queryParams = {}; const shouldUseDataAsParams = data && (typeof data === 'object') && methodHasNoBody(method); @@ -167,7 +176,7 @@ class Requestable { headers: headers, params: queryParams, data: data, - responseType: raw ? 'text' : 'json' + responseType: raw ? 'text' : 'json', }; log(`${config.method} to ${config.url}`); @@ -175,7 +184,15 @@ class Requestable { if (cb) { requestPromise.then((response) => { - cb(null, response.data || true, response); + if (response.data && Object.keys(response.data).length > 0) { + // When data has results + cb(null, response.data, response); + } else if (config.method !== 'GET' && Object.keys(response.data).length < 1) { + // True when successful submit a request and receive a empty object + cb(null, (response.status < 300), response); + } else { + cb(null, response.data, response); + } }); } @@ -198,7 +215,7 @@ class Requestable { } return true; }, function failure(response) { - if (response.status === 404) { + if (response.response.status === 404) { if (cb) { cb(null, false, response); } @@ -218,7 +235,7 @@ class Requestable { * @param {string} path - the path to request * @param {Object} options - the query parameters to include * @param {Requestable.callback} [cb] - the function to receive the data. The returned data will always be an array. - * @param {Object[]} results - the partial results. This argument is intended for interal use only. + * @param {Object[]} results - the partial results. This argument is intended for internal use only. * @return {Promise} - a promise which will resolve when all pages have been fetched * @deprecated This will be folded into {@link Requestable#_request} in the 2.0 release. */ @@ -236,12 +253,23 @@ class Requestable { let message = `cannot figure out how to append ${response.data} to the result set`; throw new ResponseError(message, path, response); } - results.push.apply(results, thisGroup); + results.push(...thisGroup); const nextUrl = getNextPage(response.headers.link); - if (nextUrl) { - log(`getting next page: ${nextUrl}`); - return this._requestAllPages(nextUrl, options, cb, results); + if(nextUrl) { + if (!options) { + options = {}; + } + options.page = parseInt( + nextUrl.match(/([&\?]page=[0-9]*)/g) + .shift() + .split('=') + .pop() + ); + if (!(options && typeof options.page !== 'number')) { + log(`getting next page: ${nextUrl}`); + return this._requestAllPages(nextUrl, options, cb, results); + } } if (cb) { @@ -276,10 +304,16 @@ function getNextPage(linksHeader = '') { } function callbackErrorOrThrow(cb, path) { - return function handler(response) { - let message = `error making request ${response.config.method} ${response.config.url}`; - let error = new ResponseError(message, path, response); - log(`${message} ${JSON.stringify(response.data)}`); + return function handler(object) { + let error; + if (object.hasOwnProperty('config')) { + const {response: {status, statusText}, config: {method, url}} = object; + let message = (`${status} error making request ${method} ${url}: "${statusText}"`); + error = new ResponseError(message, path, object); + log(`${message} ${JSON.stringify(object.data)}`); + } else { + error = object; + } if (cb) { log('going to error callback'); cb(error); diff --git a/lib/User.js b/lib/User.js index be86c4fc..a6f22324 100644 --- a/lib/User.js +++ b/lib/User.js @@ -81,6 +81,26 @@ class User extends Requestable { return this._request('GET', this.__getScopedUrl('orgs'), null, cb); } + /** + * List followers of a user + * @see https://developer.github.com/v3/users/followers/#list-followers-of-a-user + * @param {Requestable.callback} [cb] - will receive the list of followers + * @return {Promise} - the promise for the http request + */ + listFollowers(cb) { + return this._request('GET', this.__getScopedUrl('followers'), null, cb); + } + + /** + * Lists the people who the authenticated user follows. + * @see https://docs.github.com/en/rest/reference/users#list-the-people-the-authenticated-user-follows + * @param {Requestable.callback} [cb] - will receive the list of who a user is following + * @return {Promise} - the promise for the http request + */ + listFollowing(cb) { + return this._request('GET', this.__getScopedUrl('following'), null, cb); + } + /** * List the user's gists * @see https://developer.github.com/v3/gists/#list-a-users-gists @@ -132,6 +152,33 @@ class User extends Requestable { return this._requestAllPages(this.__getScopedUrl('starred'), requestOptions, cb); } + /** + * Gets the list of starred gists for the user + * @see https://developer.github.com/v3/gists/#list-starred-gists + * @param {Object} [options={}] - any options to refine the search + * @param {Requestable.callback} [cb] - will receive the list of gists + * @return {Promise} - the promise for the http request + */ + listStarredGists(options, cb) { + options = options || {}; + if (typeof options === 'function') { + cb = options; + options = {}; + } + options.since = this._dateToISO(options.since); + return this._request('GET', '/gists/starred', options, cb); + } + + /** + * List email addresses for a user + * @see https://developer.github.com/v3/users/emails/#list-email-addresses-for-a-user + * @param {Requestable.callback} [cb] - will receive the list of emails + * @return {Promise} - the promise for the http request + */ + getEmails(cb) { + return this._request('GET', '/user/emails', null, cb); + } + /** * Have the authenticated user follow this user * @see https://developer.github.com/v3/users/followers/#follow-a-user @@ -140,7 +187,7 @@ class User extends Requestable { * @return {Promise} - the promise for the http request */ follow(username, cb) { - return this._request('PUT', `/user/following/${this.__user}`, null, cb); + return this._request('PUT', `/user/following/${username}`, null, cb); } /** @@ -151,7 +198,7 @@ class User extends Requestable { * @return {Promise} - the promise for the http request */ unfollow(username, cb) { - return this._request('DELETE', `/user/following/${this.__user}`, null, cb); + return this._request('DELETE', `/user/following/${username}`, null, cb); } /** diff --git a/mocha.opts b/mocha.opts index 4944011d..0377ab2e 100644 --- a/mocha.opts +++ b/mocha.opts @@ -1,3 +1,3 @@ --compilers js:babel-register ---timeout 15000 +--timeout 20000 --slow 5000 diff --git a/package.json b/package.json index 6f15d8f6..8e54341a 100644 --- a/package.json +++ b/package.json @@ -1,6 +1,6 @@ { "name": "github-api", - "version": "2.3.0", + "version": "3.4.0", "license": "BSD-3-Clause-Clear", "description": "A higher-level wrapper around the Github API.", "main": "dist/components/GitHub.js", @@ -8,17 +8,20 @@ "Ændrew Rininsland (http://www.aendrew.com)", "Aurelio De Rosa (http://www.audero.it/)", "Clay Reimann (http://clayreimann.me)", - "Michael Aufreiter (http://substance.io)" + "Michael Aufreiter (http://substance.io)", + "Mathieu Dutour (https://github.com/mathieudutour)" ], "readmeFilename": "README.md", "scripts": { "clean": "gulp clean", "build": "gulp build", "test": "mocha --opts ./mocha.opts test/*.spec.js", + "test-coverage": "NODE_ENV=test nyc mocha --opts ./mocha.opts test/*.spec.js", "test-verbose": "DEBUG=github* npm test", "lint": "gulp lint", "make-docs": "node_modules/.bin/jsdoc -c .jsdoc.json --verbose", - "release": "./release.sh" + "release": "./release.sh", + "codecov": "nyc report --reporter=text-lcov > coverage.lcov && codecov" }, "babel": { "presets": [ @@ -26,33 +29,38 @@ ], "plugins": [ [ - "transform-es2015-modules-umd", - { - "globals": { - "es6-promise": "Promise" - } - } + "add-module-exports", + "transform-es2015-modules-umd" ] ], "env": { "development": { "sourceMaps": "inline" + }, + "test": { + "plugins": [ + "istanbul" + ] } } }, + "nyc": { + "sourceMap": false, + "instrument": false + }, "files": [ - "dist/*", - "lib/*" + "dist/*" ], "dependencies": { - "axios": "^0.10.0", + "axios": "^0.21.1", "debug": "^2.2.0", - "es6-promise": "^3.0.2", "js-base64": "^2.1.9", "utf8": "^2.1.1" }, "devDependencies": { "babel-core": "^6.7.7", + "babel-plugin-add-module-exports": "^0.2.1", + "babel-plugin-istanbul": "3.0.0", "babel-plugin-transform-es2015-modules-umd": "^6.5.0", "babel-preset-es2015": "^6.5.0", "babel-register": "^6.7.2", @@ -60,32 +68,34 @@ "browserify": "^13.0.0", "codecov": "^1.0.1", "del": "^2.2.0", - "eslint-config-google": "^0.5.0", - "eslint-plugin-mocha": "^2.2.0", + "eslint-config-google": "^0.7.0", + "eslint-plugin-mocha": "^4.7.0", "gulp": "^3.9.0", "gulp-babel": "^6.1.2", - "gulp-eslint": "^2.0.0", - "gulp-jscs": "^3.0.2", + "gulp-eslint": "^3.0.1", + "gulp-jscs": "^4.0.0", "gulp-jscs-stylish": "^1.3.0", "gulp-rename": "^1.2.2", - "gulp-sourcemaps": "^1.6.0", - "gulp-uglify": "^1.5.1", + "gulp-sourcemaps": "^2.2.0", + "gulp-uglify": "^2.0.0", "jsdoc": "^3.4.0", "minami": "^1.1.1", - "mocha": "^2.3.4", + "mocha": "^3.1.2", "must": "^0.13.1", + "nock": "^9.0.2", + "nyc": "9.0.1", "vinyl-buffer": "^1.0.0", "vinyl-source-stream": "^1.1.0" }, "repository": { "type": "git", - "url": "git://github.com/michael/github.git" + "url": "git://github.com/github-tools/github.git" }, "keywords": [ "github", "api" ], "bugs": { - "url": "https://github.com/michael/github/issues" + "url": "https://github.com/github-tools/github/issues" } } diff --git a/release.sh b/release.sh index 0b3a3c37..63853dae 100755 --- a/release.sh +++ b/release.sh @@ -1,15 +1,18 @@ #!/bin/bash # This is the automated release script -# make sure all our dependencies are installed so we can publish docs -npm install - # guard against stupid if [ -z "$1" ]; then echo "You must specify a new version level: [patch, minor, major]"; exit 1; fi +# make sure all our dependencies are installed so we can publish docs +npm install + +# try to build to make sure we don't publish something really broken +npm run build + # bump the version echo "npm version $1" npm version $1 @@ -32,5 +35,5 @@ git checkout gh-pages mv out/* docs/ echo $VERSION >> _data/versions.csv git add . -git co -m "adding docs for v$VERSION" +git commit -m "adding docs for v$VERSION" git push diff --git a/test/auth.spec.js b/test/auth.spec.js index 2c03d766..00842ee9 100644 --- a/test/auth.spec.js +++ b/test/auth.spec.js @@ -1,7 +1,7 @@ import expect from 'must'; import Github from '../lib/GitHub'; -import testUser from './fixtures/user.json'; +import testUser from './fixtures/user.js'; import {assertSuccessful, assertFailure} from './helpers/callbacks'; describe('Github', function() { @@ -13,7 +13,7 @@ describe('Github', function() { github = new Github({ username: testUser.USERNAME, password: testUser.PASSWORD, - auth: 'basic' + auth: 'basic', }); user = github.getUser(); @@ -70,7 +70,7 @@ describe('Github', function() { github = new Github({ username: testUser.USERNAME, password: 'fake124', - auth: 'basic' + auth: 'basic', }); user = github.getUser(); @@ -83,8 +83,8 @@ describe('Github', function() { it('should fail authentication and return err', function(done) { user.listNotifications(assertFailure(done, function(err) { - expect(err.status).to.be.equal(401, 'Return 401 status for bad auth'); - expect(err.response.data.message).to.equal('Bad credentials'); + expect(err.response.status).to.be.equal(401, 'Return 401 status for bad auth'); + expect(err.response.data.message).to.equal('Requires authentication'); done(); })); diff --git a/test/dist.spec/index.html b/test/dist.spec/index.html index da5a21de..b0a32d7b 100644 --- a/test/dist.spec/index.html +++ b/test/dist.spec/index.html @@ -12,7 +12,7 @@