diff --git a/.travis.yml b/.travis.yml index 26f56aa3..6b6bad91 100644 --- a/.travis.yml +++ b/.travis.yml @@ -1,24 +1,21 @@ sudo: false language: node_js node_js: - - '6' - - '5' - - '4' - +- '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 run test-coverage - # - npm run test-dist # test the compiled files +- npm run test-coverage after_success: - - npm run codecov +- npm run codecov before_deploy: - - npm run build +- npm run build deploy: provider: npm skip_cleanup: true @@ -26,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/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 919b5eb3..5e5f527f 100644 --- a/README.md +++ b/README.md @@ -1,3 +1,6 @@ +# 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] @@ -6,7 +9,7 @@ [![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] -Github.js provides a minimal higher-level wrapper around Github's API. +`Github.js` provides a minimal higher-level wrapper around Github's API. ## Usage @@ -68,7 +71,7 @@ clayreimann.listStarredRepos(function(err, repos) { should include updated JSDoc. ## Installation -Github.js is available from `npm` or [unpkg][unpkg]. +`Github.js` is available from `npm` or [unpkg][unpkg]. ```shell npm install github-api @@ -83,10 +86,7 @@ npm install github-api ``` ## Compatibility -`Github.js` is tested on Node.js: -* 6.x - -Note: `Github.js` uses Promise, hence it will not work in Node.js < 4 without polyfill. +`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/ @@ -94,3 +94,33 @@ Note: `Github.js` uses Promise, hence it will not work in Node.js < 4 without po [npm-package]: https://www.npmjs.com/package/github-api/ [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/lib/GitHub.js b/lib/GitHub.js index 59cb94ff..7c3ac24c 100644 --- a/lib/GitHub.js +++ b/lib/GitHub.js @@ -34,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) { @@ -71,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} */ @@ -81,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} */ @@ -117,7 +117,7 @@ class GitHub { /** * Create a new Project wrapper * @param {string} id - the id of the project - * @return {Markdown} + * @return {Project} */ getProject(id) { return new Project(id, this.__auth, this.__apiBase); diff --git a/lib/Markdown.js b/lib/Markdown.js index edc346cc..ebfbb512 100644 --- a/lib/Markdown.js +++ b/lib/Markdown.js @@ -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/Repository.js b/lib/Repository.js index fb200397..74452dde 100644 --- a/lib/Repository.js +++ b/lib/Repository.js @@ -14,7 +14,7 @@ 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 { /** @@ -188,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 @@ -231,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 @@ -334,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], }; + 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 @@ -494,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 @@ -603,7 +656,7 @@ class Repository extends Requestable { * @return {Promise} - the promise for the http request */ deleteHook(id, cb) { - return this._request('DELETE', `${this.__fullname}/hooks/${id}`, null, cb); + return this._request('DELETE', `/repos/${this.__fullname}/hooks/${id}`, null, cb); } /** @@ -713,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 = {}; diff --git a/lib/Requestable.js b/lib/Requestable.js index bad111ac..4d6e8d9d 100644 --- a/lib/Requestable.js +++ b/lib/Requestable.js @@ -124,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) { @@ -235,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. */ @@ -256,9 +256,20 @@ class Requestable { results.push(...thisGroup); const nextUrl = getNextPage(response.headers.link); - if (nextUrl && typeof options.page !== 'number') { - 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) { diff --git a/lib/User.js b/lib/User.js index 3f3b4bb6..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,23 @@ 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 @@ -150,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); } /** @@ -161,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 2ea2eb9a..8e54341a 100644 --- a/package.json +++ b/package.json @@ -1,6 +1,6 @@ { "name": "github-api", - "version": "3.1.0", + "version": "3.4.0", "license": "BSD-3-Clause-Clear", "description": "A higher-level wrapper around the Github API.", "main": "dist/components/GitHub.js", @@ -52,7 +52,7 @@ "dist/*" ], "dependencies": { - "axios": "^0.15.2", + "axios": "^0.21.1", "debug": "^2.2.0", "js-base64": "^2.1.9", "utf8": "^2.1.1" diff --git a/test/auth.spec.js b/test/auth.spec.js index bd0d8205..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() { @@ -84,7 +84,7 @@ describe('Github', function() { it('should fail authentication and return err', function(done) { user.listNotifications(assertFailure(done, function(err) { expect(err.response.status).to.be.equal(401, 'Return 401 status for bad auth'); - expect(err.response.data.message).to.equal('Bad credentials'); + 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 @@