@octokit-next/core
Advanced tools
Comparing version 2.4.1 to 2.5.0
157
index.js
@@ -1,8 +0,21 @@ | ||
import { request as coreRequest } from "@octokit-next/request"; | ||
import { endpoint } from "@octokit-next/endpoint"; | ||
import { request } from "@octokit-next/request"; | ||
import { createTokenAuth } from "@octokit-next/auth-token"; | ||
import { withCustomRequest } from "@octokit-next/graphql"; | ||
import { getUserAgent } from "universal-user-agent"; | ||
import Hook from "before-after-hook"; | ||
import { VERSION } from "./lib/version.js"; | ||
export class Octokit { | ||
static VERSION = VERSION; | ||
static DEFAULTS = { | ||
baseUrl: endpoint.DEFAULTS.baseUrl, | ||
userAgent: `octokit-next-core.js/${VERSION} ${getUserAgent()}`, | ||
}; | ||
static withPlugins(newPlugins) { | ||
const currentPlugins = this.plugins; | ||
const currentPlugins = this.PLUGINS; | ||
return class extends this { | ||
static plugins = currentPlugins.concat( | ||
static PLUGINS = currentPlugins.concat( | ||
newPlugins.filter((plugin) => !currentPlugins.includes(plugin)) | ||
@@ -14,37 +27,131 @@ ); | ||
static withDefaults(defaults) { | ||
const newDefaultUserAgent = [defaults?.userAgent, this.DEFAULTS.userAgent] | ||
.filter(Boolean) | ||
.join(" "); | ||
const newDefaults = { | ||
...this.DEFAULTS, | ||
...defaults, | ||
userAgent: newDefaultUserAgent, | ||
request: { | ||
...this.DEFAULTS.request, | ||
...defaults?.request, | ||
}, | ||
}; | ||
return class extends this { | ||
constructor(options) { | ||
super({ | ||
...defaults, | ||
...options, | ||
}); | ||
if (typeof defaults === "function") { | ||
super(defaults(options, newDefaults)); | ||
return; | ||
} | ||
super(options); | ||
} | ||
static defaults = { ...defaults, ...this.defaults }; | ||
static DEFAULTS = newDefaults; | ||
}; | ||
} | ||
static plugins = []; | ||
static defaults = { | ||
baseUrl: "https://api.github.com", | ||
}; | ||
static PLUGINS = []; | ||
constructor(options = {}) { | ||
this.options = { ...this.constructor.defaults, ...options }; | ||
this.constructor.plugins.forEach((plugin) => { | ||
Object.assign(this, plugin(this, options)); | ||
}); | ||
} | ||
this.options = { | ||
...this.constructor.DEFAULTS, | ||
...options, | ||
request: { | ||
...this.constructor.DEFAULTS.request, | ||
...options?.request, | ||
}, | ||
}; | ||
request(route, parameters = {}) { | ||
const requestOptions = { | ||
...this.options.request, | ||
...parameters.request, | ||
const hook = new Hook.Collection(); | ||
const requestDefaults = { | ||
baseUrl: this.options.baseUrl, | ||
headers: {}, | ||
request: { | ||
...this.options.request, | ||
hook: hook.bind(null, "request"), | ||
}, | ||
mediaType: { | ||
previews: [], | ||
format: "", | ||
}, | ||
}; | ||
return coreRequest(route, { | ||
...{ baseUrl: this.options.baseUrl }, | ||
...parameters, | ||
request: requestOptions, | ||
// prepend default user agent with `options.userAgent` if set | ||
const userAgent = [options?.userAgent, this.constructor.DEFAULTS.userAgent] | ||
.filter(Boolean) | ||
.join(" "); | ||
requestDefaults.headers["user-agent"] = userAgent; | ||
if (this.options.previews) { | ||
requestDefaults.mediaType.previews = this.options.previews; | ||
} | ||
if (this.options.timeZone) { | ||
requestDefaults.headers["time-zone"] = this.options.timeZone; | ||
} | ||
// Apply plugins | ||
this.constructor.PLUGINS.forEach((plugin) => { | ||
Object.assign(this, plugin(this, this.options)); | ||
}); | ||
// API | ||
this.request = request.defaults(requestDefaults); | ||
this.graphql = withCustomRequest(this.request).defaults(requestDefaults); | ||
this.log = Object.assign( | ||
{ | ||
debug: () => {}, | ||
info: () => {}, | ||
warn: console.warn.bind(console), | ||
error: console.error.bind(console), | ||
}, | ||
this.options.log | ||
); | ||
this.hook = hook; | ||
// Auth | ||
// (1) If neither `options.authStrategy` nor `options.auth` are set, the `octokit` instance | ||
// is unauthenticated. The `this.auth()` method is a no-op and no request hook is registered. | ||
// (2) If only `options.auth` is set, use the default token authentication strategy. | ||
// (3) If `options.authStrategy` is set then use it and pass in `options.auth`. Always pass own request as many strategies accept a custom request instance. | ||
if (!this.options.authStrategy) { | ||
if (!this.options.auth) { | ||
// (1) | ||
this.auth = async () => ({ | ||
type: "unauthenticated", | ||
}); | ||
} else { | ||
// (2) | ||
const auth = createTokenAuth({ token: this.options.auth }); | ||
hook.wrap("request", auth.hook); | ||
this.auth = auth; | ||
} | ||
} else { | ||
// (3) | ||
const { authStrategy, ...otherOptions } = this.options; | ||
const auth = authStrategy( | ||
Object.assign( | ||
{ | ||
request: this.request, | ||
log: this.log, | ||
// we pass the current octokit instance as well as its constructor options | ||
// to allow for authentication strategies that return a new octokit instance | ||
// that shares the same internal state as the current one. The original | ||
// requirement for this was the "event-octokit" authentication strategy | ||
// of https://github.com/probot/octokit-auth-probot. | ||
octokit: this, | ||
octokitOptions: otherOptions, | ||
}, | ||
this.options.auth | ||
) | ||
); | ||
hook.wrap("request", auth.hook); | ||
this.auth = auth; | ||
} | ||
} | ||
} |
@@ -7,3 +7,3 @@ { | ||
"type": "module", | ||
"version": "2.4.1", | ||
"version": "2.5.0", | ||
"description": "Core SDK module for upcoming Octokit", | ||
@@ -18,9 +18,30 @@ "exports": "./index.js", | ||
"homepage": "https://github.com/octokit/octokit-next.js/tree/main/packages/core#readme", | ||
"keywords": [ | ||
"octokit", | ||
"github", | ||
"api" | ||
], | ||
"author": "Gregor Martynus (https://twitter.com/gr2m)", | ||
"license": "MIT", | ||
"scripts": { | ||
"test": "npm run test:code && npm run test:types", | ||
"test:code": "c8 ava test", | ||
"test:types": "tsd" | ||
}, | ||
"dependencies": { | ||
"@octokit-next/auth-token": "2.4.1", | ||
"@octokit-next/request": "2.4.1", | ||
"@octokit-next/types": "2.4.1" | ||
"@octokit-next/auth-token": "2.5.0", | ||
"@octokit-next/endpoint": "2.5.0", | ||
"@octokit-next/graphql": "2.5.0", | ||
"@octokit-next/request": "2.5.0", | ||
"@octokit-next/types": "2.5.0", | ||
"before-after-hook": "^3.0.2", | ||
"universal-user-agent": "^7.0.1" | ||
}, | ||
"c8": { | ||
"check-coverage": true, | ||
"lines": 10, | ||
"functions": 10, | ||
"branches": 10, | ||
"statements": 10 | ||
} | ||
} |
444
README.md
@@ -1,7 +0,443 @@ | ||
# `@octokit-next/core` | ||
# core.js | ||
> Core SDK module for upcoming Octokit | ||
> Extendable client for GitHub's REST & GraphQL APIs | ||
🚫⚠️ This package is part of an experimental Octokit SDK for testing purpose only - DO NOT USE | ||
[![@latest](https://img.shields.io/npm/v/@octokit-next/core.svg)](https://www.npmjs.com/package/@octokit-next/core) | ||
[![Build Status](https://github.com/octokit-next/core.js/workflows/Test/badge.svg)](https://github.com/octokit-next/core.js/actions?query=workflow%3ATest+branch%3Amaster) | ||
[learn more](https://github.com/octokit/octokit-next.js) | ||
If you need a minimalistic library to utilize GitHub's [REST API](https://developer.github.com/v3/) and [GraphQL API](https://developer.github.com/v4/) which you can extend with plugins as needed, then `@octokit-next/core` is a great starting point. | ||
If you don't need the Plugin API then using [`@octokit-next/request`](https://github.com/octokit-next/request.js/) or [`@octokit-next/graphql`](https://github.com/octokit-next/graphql.js/) directly is a good alternative. | ||
## Usage | ||
<table> | ||
<tbody valign=top align=left> | ||
<tr><th> | ||
Browsers | ||
</th><td width=100%> | ||
Load <code>@octokit-next/core</code> directly from <a href="https://cdn.skypack.dev">cdn.skypack.dev</a> | ||
```html | ||
<script type="module"> | ||
import { Octokit } from "https://cdn.skypack.dev/@octokit-next/core"; | ||
</script> | ||
``` | ||
</td></tr> | ||
<tr><th> | ||
Node | ||
</th><td> | ||
Install with <code>npm install @octokit-next/core</code> | ||
```js | ||
import { Octokit } from "@octokit-next/core"; | ||
``` | ||
</td></tr> | ||
<tr><th> | ||
Deno | ||
</th><td> | ||
Load <code>@octokit-next/core</code> directly from <a href="https://cdn.skypack.dev">cdn.skypack.dev</a>, including types. | ||
```js | ||
import { Octokit } from "https://cdn.skypack.dev/@octokit-next/core?dts"; | ||
``` | ||
</td></tr> | ||
</tbody> | ||
</table> | ||
### REST API example | ||
```js | ||
// Create a personal access token at https://github.com/settings/tokens/new?scopes=repo | ||
const octokit = new Octokit({ auth: `personal-access-token123` }); | ||
const response = await octokit.request("GET /orgs/{org}/repos", { | ||
org: "octokit", | ||
type: "private", | ||
}); | ||
``` | ||
See [`@octokit-next/request`](https://github.com/octokit-next/request.js) for full documentation of the `.request` method. | ||
### GraphQL example | ||
```js | ||
const octokit = new Octokit({ auth: `secret123` }); | ||
const response = await octokit.graphql( | ||
`query ($login: String!) { | ||
organization(login: $login) { | ||
repositories(privacy: PRIVATE) { | ||
totalCount | ||
} | ||
} | ||
}`, | ||
{ login: "octokit" } | ||
); | ||
``` | ||
See [`@octokit-next/graphql`](https://github.com/octokit-next/graphql.js) for full documentation of the `.graphql` method. | ||
## Options | ||
<table> | ||
<thead align=left> | ||
<tr> | ||
<th> | ||
name | ||
</th> | ||
<th> | ||
type | ||
</th> | ||
<th width=100%> | ||
description | ||
</th> | ||
</tr> | ||
</thead> | ||
<tbody align=left valign=top> | ||
<tr> | ||
<th> | ||
<code>options.authStrategy</code> | ||
</th> | ||
<td> | ||
<code>Function<code> | ||
</td> | ||
<td> | ||
Defaults to <a href="https://github.com/octokit-next/auth-token.js#readme"><code>@octokit-next/auth-token</code></a>. See <a href="#authentication">Authentication</a> below for examples. | ||
</td> | ||
</tr> | ||
<tr> | ||
<th> | ||
<code>options.auth</code> | ||
</th> | ||
<td> | ||
<code>String</code> or <code>Object</code> | ||
</td> | ||
<td> | ||
See <a href="#authentication">Authentication</a> below for examples. | ||
</td> | ||
</tr> | ||
<tr> | ||
<th> | ||
<code>options.baseUrl</code> | ||
</th> | ||
<td> | ||
<code>String</code> | ||
</td> | ||
<td> | ||
When using with GitHub Enterprise Server, set `options.baseUrl` to the root URL of the API. For example, if your GitHub Enterprise Server's hostname is `github.acme-inc.com`, then set `options.baseUrl` to `https://github.acme-inc.com/api/v3`. Example | ||
```js | ||
const octokit = new Octokit({ | ||
baseUrl: "https://github.acme-inc.com/api/v3", | ||
}); | ||
``` | ||
</td></tr> | ||
<tr> | ||
<th> | ||
<code>options.previews</code> | ||
</th> | ||
<td> | ||
<code>Array of Strings</code> | ||
</td> | ||
<td> | ||
Some REST API endpoints require preview headers to be set, or enable | ||
additional features. Preview headers can be set on a per-request basis, e.g. | ||
```js | ||
octokit.request("POST /repos/{owner}/{repo}/pulls", { | ||
mediaType: { | ||
previews: ["shadow-cat"], | ||
}, | ||
owner, | ||
repo, | ||
title: "My pull request", | ||
base: "master", | ||
head: "my-feature", | ||
draft: true, | ||
}); | ||
``` | ||
You can also set previews globally, by setting the `options.previews` option on the constructor. Example: | ||
```js | ||
const octokit = new Octokit({ | ||
previews: ["shadow-cat"], | ||
}); | ||
``` | ||
</td></tr> | ||
<tr> | ||
<th> | ||
<code>options.request</code> | ||
</th> | ||
<td> | ||
<code>Object</code> | ||
</td> | ||
<td> | ||
Set a default request timeout (`options.request.timeout`) or an [`http(s).Agent`](https://nodejs.org/api/http.html#http_class_http_agent) e.g. for proxy usage (Node only, `options.request.agent`). | ||
There are more `options.request.*` options, see [`@octokit-next/request` options](https://github.com/octokit-next/request.js#request). `options.request` can also be set on a per-request basis. | ||
</td></tr> | ||
<tr> | ||
<th> | ||
<code>options.timeZone</code> | ||
</th> | ||
<td> | ||
<code>String</code> | ||
</td> | ||
<td> | ||
Sets the `Time-Zone` header which defines a timezone according to the [list of names from the Olson database](https://en.wikipedia.org/wiki/List_of_tz_database_time_zones). | ||
```js | ||
const octokit = new Octokit({ | ||
timeZone: "America/Los_Angeles", | ||
}); | ||
``` | ||
The time zone header will determine the timezone used for generating the timestamp when creating commits. See [GitHub's Timezones documentation](https://developer.github.com/v3/#timezones). | ||
</td></tr> | ||
<tr> | ||
<th> | ||
<code>options.userAgent</code> | ||
</th> | ||
<td> | ||
<code>String</code> | ||
</td> | ||
<td> | ||
A custom user agent string for your app or library. Example | ||
```js | ||
const octokit = new Octokit({ | ||
userAgent: "my-app/v1.2.3", | ||
}); | ||
``` | ||
</td></tr> | ||
</tbody> | ||
</table> | ||
## Defaults | ||
You can create a new Octokit class with customized default options. | ||
```js | ||
const MyOctokit = Octokit.withDefaults({ | ||
auth: "personal-access-token123", | ||
baseUrl: "https://github.acme-inc.com/api/v3", | ||
userAgent: "my-app/v1.2.3", | ||
}); | ||
const octokit1 = new MyOctokit(); | ||
const octokit2 = new MyOctokit(); | ||
``` | ||
If you pass additional options to your new constructor, the options will be merged shallowly. | ||
```js | ||
const MyOctokit = Octokit.withDefaults({ | ||
foo: { | ||
opt1: 1, | ||
}, | ||
}); | ||
const octokit = new MyOctokit({ | ||
foo: { | ||
opt2: 1, | ||
}, | ||
}); | ||
// options will be { foo: { opt2: 1 }} | ||
``` | ||
If you need a deep or conditional merge, you can pass a function instead. | ||
```js | ||
const MyOctokit = Octokit.withDefaults((options) => { | ||
return { | ||
foo: Object.assign({}, options.foo, { opt1: 1 }), | ||
}; | ||
}); | ||
const octokit = new MyOctokit({ | ||
foo: { opt2: 1 }, | ||
}); | ||
// options will be { foo: { opt1: 1, opt2: 1 }} | ||
``` | ||
Be careful about mutating the `options` object in the `Octokit.withDefaults` callback, as it can have unforeseen consequences. | ||
## Authentication | ||
Authentication is optional for some REST API endpoints accessing public data, but is required for GraphQL queries. Using authentication also increases your [API rate limit](https://developer.github.com/v3/#rate-limiting). | ||
By default, Octokit authenticates using the [token authentication strategy](https://github.com/octokit-next/auth-token.js). Pass in a token using `options.auth`. It can be a personal access token, an OAuth token, an installation access token or a JSON Web Token for GitHub App authentication. The `Authorization` header will be set according to the type of token. | ||
```js | ||
import { Octokit } from "@octokit-next/core"; | ||
const octokit = new Octokit({ | ||
auth: "mypersonalaccesstoken123", | ||
}); | ||
const { data } = await octokit.request("/user"); | ||
``` | ||
To use a different authentication strategy, set `options.authStrategy`. A list of authentication strategies is available at [octokit-next/authentication-strategies.js](https://github.com/octokit-next/authentication-strategies.js/#readme). | ||
Example | ||
```js | ||
import { Octokit } from "@octokit-next/core"; | ||
import { createAppAuth } from "@octokit-next/auth-app"; | ||
const appOctokit = new Octokit({ | ||
authStrategy: createAppAuth, | ||
auth: { | ||
appId: 123, | ||
privateKey: process.env.PRIVATE_KEY, | ||
}, | ||
}); | ||
const { data } = await appOctokit.request("/app"); | ||
``` | ||
The `.auth()` method returned by the current authentication strategy can be accessed at `octokit.auth()`. Example | ||
```js | ||
const { token } = await appOctokit.auth({ | ||
type: "installation", | ||
installationId: 123, | ||
}); | ||
``` | ||
## Logging | ||
There are four built-in log methods | ||
1. `octokit.log.debug(message[, additionalInfo])` | ||
1. `octokit.log.info(message[, additionalInfo])` | ||
1. `octokit.log.warn(message[, additionalInfo])` | ||
1. `octokit.log.error(message[, additionalInfo])` | ||
They can be configured using the [`log` client option](client-options). By default, `octokit.log.debug()` and `octokit.log.info()` are no-ops, while the other two call `console.warn()` and `console.error()` respectively. | ||
This is useful if you build reusable [plugins](#plugins). | ||
If you would like to make the log level configurable using an environment variable or external option, we recommend the [console-log-level](https://github.com/watson/console-log-level) package. Example | ||
```js | ||
const octokit = new Octokit({ | ||
log: require("console-log-level")({ level: "info" }), | ||
}); | ||
``` | ||
## Hooks | ||
You can customize Octokit's request lifecycle with hooks. | ||
```js | ||
octokit.hook.before("request", async (options) => { | ||
validate(options); | ||
}); | ||
octokit.hook.after("request", async (response, options) => { | ||
console.log(`${options.method} ${options.url}: ${response.status}`); | ||
}); | ||
octokit.hook.error("request", async (error, options) => { | ||
if (error.status === 304) { | ||
return findInCache(error.response.headers.etag); | ||
} | ||
throw error; | ||
}); | ||
octokit.hook.wrap("request", async (request, options) => { | ||
// add logic before, after, catch errors or replace the request altogether | ||
return request(options); | ||
}); | ||
``` | ||
See [before-after-hook](https://github.com/gr2m/before-after-hook#readme) for more documentation on hooks. | ||
## Plugins | ||
Octokit’s functionality can be extended using plugins. The `Octokit.withPlugins()` method accepts a plugin (or many) and returns a new constructor. | ||
A plugin is a function which gets two arguments: | ||
1. the current instance | ||
2. the options passed to the constructor. | ||
In order to extend `octokit`'s API, the plugin must return an object with the new methods. | ||
```js | ||
// index.js | ||
const { Octokit } = require("@octokit-next/core") | ||
const MyOctokit = Octokit.withPlugins([ | ||
require("./lib/my-plugin"), | ||
require("octokit-plugin-example") | ||
] | ||
); | ||
const octokit = new MyOctokit({ greeting: "Moin moin" }); | ||
octokit.helloWorld(); // logs "Moin moin, world!" | ||
octokit.request("GET /"); // logs "GET / - 200 in 123ms" | ||
// lib/my-plugin.js | ||
module.exports = (octokit, options = { greeting: "Hello" }) => { | ||
// hook into the request lifecycle | ||
octokit.hook.wrap("request", async (request, options) => { | ||
const time = Date.now(); | ||
const response = await request(options); | ||
console.log( | ||
`${options.method} ${options.url} – ${response.status} in ${Date.now() - | ||
time}ms` | ||
); | ||
return response; | ||
}); | ||
// add a custom method | ||
return { | ||
helloWorld: () => console.log(`${options.greeting}, world!`); | ||
} | ||
}; | ||
``` | ||
## Build your own Octokit with Plugins and Defaults | ||
You can build your own Octokit class with preset default options and plugins. In fact, this is mostly how the `@octokit-next/<context>` modules work, such as [`@octokit-next/action`](https://github.com/octokit-next/action.js): | ||
```js | ||
const { Octokit } = require("@octokit-next/core"); | ||
const MyActionOctokit = Octokit.withPlugins([ | ||
require("@octokit-next/plugin-paginate-rest").paginateRest, | ||
require("@octokit-next/plugin-throttling").throttling, | ||
require("@octokit-next/plugin-retry").retry, | ||
]).withDefaults({ | ||
throttle: { | ||
onAbuseLimit: (retryAfter, options) => { | ||
/* ... */ | ||
}, | ||
onRateLimit: (retryAfter, options) => { | ||
/* ... */ | ||
}, | ||
}, | ||
authStrategy: require("@octokit-next/auth-action").createActionAuth, | ||
userAgent: `my-octokit-action/v1.2.3`, | ||
}); | ||
const octokit = new MyActionOctokit(); | ||
const installations = await octokit.paginate("GET /app/installations"); | ||
``` | ||
## LICENSE | ||
[MIT](LICENSE) |
Major refactor
Supply chain riskPackage has recently undergone a major refactor. It may be unstable or indicate significant internal changes. Use caution when updating to versions that include significant changes.
Found 1 instance in 1 package
No tests
QualityPackage does not have any tests. This is a strong signal of a poorly maintained or low quality package.
Found 1 instance in 1 package
57976
21
1454
0
444
7
1
+ Added@octokit-next/endpoint@2.5.0
+ Added@octokit-next/graphql@2.5.0
+ Addedbefore-after-hook@^3.0.2
+ Addeduniversal-user-agent@^7.0.1
+ Added@octokit-next/auth-token@2.5.0(transitive)
+ Added@octokit-next/endpoint@2.5.0(transitive)
+ Added@octokit-next/graphql@2.5.0(transitive)
+ Added@octokit-next/request@2.5.0(transitive)
+ Added@octokit-next/request-error@2.8.0(transitive)
+ Added@octokit-next/types@2.5.02.8.0(transitive)
+ Addedbefore-after-hook@3.0.2(transitive)
+ Addedis-plain-obj@4.1.0(transitive)
+ Addedtype-fest@4.18.2(transitive)
- Removed@octokit-next/auth-token@2.4.1(transitive)
- Removed@octokit-next/request@2.4.1(transitive)
- Removed@octokit-next/types@2.4.1(transitive)
Updated@octokit-next/request@2.5.0
Updated@octokit-next/types@2.5.0