Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
12 changes: 6 additions & 6 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -342,7 +342,7 @@ See [before-after-hook](https://github.com/gr2m/before-after-hook#readme) for

## Plugins

Octokit’s functionality can be extended using plugins. THe `Octokit.plugin()` method accepts a function or an array of functions and returns a new constructor.
Octokit’s functionality can be extended using plugins. The `Octokit.plugin()` method accepts a plugin (or many) and returns a new constructor.
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Maybe we should add a note specifically to TypeScript users. Something like this:

Octokit.plugin() also accepts a single array parameter but we recommend against it, as it breaks the TypeScript/Intellisense support

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The or many part is meant to mean plugins applied in the .plugin(p1, p2, ...) format - not an array. I've updated the syntax throughout the document to use the new, non-array, syntax here and here.

Additionally, there is now a warning logged to the console when the method is supplied with an array.

This should be enough so as to not address TypeScript users specifically - as all users (js and ts) will now be nudged into the correct syntax.


A plugin is a function which gets two arguments:

Expand All @@ -353,10 +353,10 @@ In order to extend `octokit`'s API, the plugin must return an object with the ne

```js
// index.js
const MyOctokit = require("@octokit/core").plugin([
const MyOctokit = require("@octokit/core").plugin(
require("./lib/my-plugin"),
require("octokit-plugin-example")
]);
);

const octokit = new MyOctokit({ greeting: "Moin moin" });
octokit.helloWorld(); // logs "Moin moin, world!"
Expand Down Expand Up @@ -388,11 +388,11 @@ You can build your own Octokit class with preset default options and plugins. In

```js
const MyActionOctokit = require("@octokit/core")
.plugin([
.plugin(
require("@octokit/plugin-paginate"),
require("@octokit/plugin-throttle"),
require("@octokit/plugin-retry"),
])
require("@octokit/plugin-retry")
)
.defaults({
authStrategy: require("@octokit/auth-action"),
userAgent: `my-octokit-action/v1.2.3`,
Expand Down
36 changes: 29 additions & 7 deletions src/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -10,6 +10,7 @@ import {
OctokitPlugin,
RequestParameters,
ReturnTypeOf,
UnionToIntersection,
} from "./types";
import { VERSION } from "./version";

Expand Down Expand Up @@ -42,22 +43,43 @@ export class Octokit {
}

static plugins: OctokitPlugin[] = [];
/**
* Attach a plugin (or many) to your Octokit instance.
*
* @example
* const API = Octokit.plugin(plugin1, plugin2, plugin3, ...)
*/
static plugin<
S extends Constructor<any> & { plugins: any[] },
T extends OctokitPlugin | OctokitPlugin[]
>(this: S, pluginOrPlugins: T) {
T1 extends OctokitPlugin | OctokitPlugin[],
T2 extends OctokitPlugin[]
>(this: S, p1: T1, ...p2: T2) {
if (p1 instanceof Array) {
console.warn(
[
"Passing an array of plugins to Octokit.plugin() has been deprecated.",
"Instead of:",
" Octokit.plugin([plugin1, plugin2, ...])",
"Use:",
" Octokit.plugin(plugin1, plugin2, ...)",
].join("\n")
);
}
const currentPlugins = this.plugins;
const newPlugins = Array.isArray(pluginOrPlugins)
? pluginOrPlugins
: [pluginOrPlugins];

let newPlugins: (OctokitPlugin | undefined)[] = [
...(p1 instanceof Array
? (p1 as OctokitPlugin[])
: [p1 as OctokitPlugin]),
...p2,
];
const NewOctokit = class extends this {
static plugins = currentPlugins.concat(
newPlugins.filter((plugin) => !currentPlugins.includes(plugin))
);
};

return NewOctokit as typeof NewOctokit & Constructor<ReturnTypeOf<T>>;
return NewOctokit as typeof NewOctokit &
Constructor<UnionToIntersection<ReturnTypeOf<T1> & ReturnTypeOf<T2>>>;
}

constructor(options: OctokitOptions = {}) {
Expand Down
2 changes: 1 addition & 1 deletion src/types.ts
Original file line number Diff line number Diff line change
Expand Up @@ -28,7 +28,7 @@ export type ReturnTypeOf<
* @author https://stackoverflow.com/users/2887218/jcalz
* @see https://stackoverflow.com/a/50375286/10325032
*/
type UnionToIntersection<Union> = (
export type UnionToIntersection<Union> = (
Union extends any ? (argument: Union) => void : never
) extends (argument: infer Intersection) => void // tslint:disable-line: no-unused
? Intersection
Expand Down
21 changes: 21 additions & 0 deletions test/__snapshots__/plugin.test.ts.snap
Original file line number Diff line number Diff line change
@@ -0,0 +1,21 @@
// Jest Snapshot v1, https://goo.gl/fbAQLP

exports[`Octokit.plugin() supports array of plugins and warns of deprecated usage 1`] = `
[MockFunction] {
"calls": Array [
Array [
"Passing an array of plugins to Octokit.plugin() has been deprecated.
Instead of:
Octokit.plugin([plugin1, plugin2, ...])
Use:
Octokit.plugin(plugin1, plugin2, ...)",
],
],
"results": Array [
Object {
"type": "return",
"value": undefined,
},
],
}
`;
60 changes: 27 additions & 33 deletions test/plugin.test.ts
Original file line number Diff line number Diff line change
@@ -1,41 +1,43 @@
import { Octokit } from "../src";

const pluginFoo = () => {
return { foo: "bar" };
};
const pluginBaz = () => {
return { baz: "daz" };
};
const pluginQaz = () => {
return { qaz: "naz" };
};

describe("Octokit.plugin()", () => {
it("gets called in constructor", () => {
const MyOctokit = Octokit.plugin(() => {
return {
foo: "bar",
};
});
const MyOctokit = Octokit.plugin(pluginFoo);
const myClient = new MyOctokit();
expect(myClient.foo).toEqual("bar");
});

it("supports array of plugins", () => {
const MyOctokit = Octokit.plugin([
() => {
return {
foo: "bar",
};
},
() => {
return { baz: "daz" };
},
]);
it("supports array of plugins and warns of deprecated usage", () => {
Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

In reference to #53 (comment),

We have kept the test using an array of plugins. In this test, we're also checking for the deprecation notice too.

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Sorry I forgot about it. Thanks for pointing it out

const warningSpy = jest.spyOn(console, "warn").mockImplementation();
const MyOctokit = Octokit.plugin([pluginFoo, pluginBaz]);
const myClient = new MyOctokit();
expect(myClient.foo).toEqual("bar");
expect(myClient.baz).toEqual("daz");
expect(warningSpy).toMatchSnapshot();
warningSpy.mockClear();
});

it("supports multiple plugins", () => {
const MyOctokit = Octokit.plugin(pluginFoo, pluginBaz, pluginQaz);
const myClient = new MyOctokit();
expect(myClient.foo).toEqual("bar");
expect(myClient.baz).toEqual("daz");
expect(myClient.qaz).toEqual("naz");
});
it("does not override plugins of original constructor", () => {
const MyOctokit = Octokit.plugin((octokit) => {
return {
foo: "bar",
};
});
const MyOctokit = Octokit.plugin(pluginFoo);
const myClient = new MyOctokit();
expect(myClient.foo).toEqual("bar");

const octokit = new Octokit();
expect(octokit).not.toHaveProperty("foo");
});
Expand Down Expand Up @@ -64,17 +66,9 @@ describe("Octokit.plugin()", () => {
});

it("supports chaining", () => {
const MyOctokit = Octokit.plugin(() => {
return {
foo: "bar",
};
})
.plugin(() => {
return { baz: "daz" };
})
.plugin(() => {
return { qaz: "naz" };
});
const MyOctokit = Octokit.plugin(pluginFoo)
.plugin(pluginBaz)
.plugin(pluginQaz);

const myClient = new MyOctokit();
expect(myClient.foo).toEqual("bar");
Expand Down