diff --git a/README.md b/README.md index 0256e5602..3309ce13b 100644 --- a/README.md +++ b/README.md @@ -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. A plugin is a function which gets two arguments: @@ -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!" @@ -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`, diff --git a/src/index.ts b/src/index.ts index 456297605..58b4d138a 100644 --- a/src/index.ts +++ b/src/index.ts @@ -10,6 +10,7 @@ import { OctokitPlugin, RequestParameters, ReturnTypeOf, + UnionToIntersection, } from "./types"; import { VERSION } from "./version"; @@ -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 & { 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>; + return NewOctokit as typeof NewOctokit & + Constructor & ReturnTypeOf>>; } constructor(options: OctokitOptions = {}) { diff --git a/src/types.ts b/src/types.ts index f6586f80b..ff5cfe39e 100644 --- a/src/types.ts +++ b/src/types.ts @@ -28,7 +28,7 @@ export type ReturnTypeOf< * @author https://stackoverflow.com/users/2887218/jcalz * @see https://stackoverflow.com/a/50375286/10325032 */ -type UnionToIntersection = ( +export type UnionToIntersection = ( Union extends any ? (argument: Union) => void : never ) extends (argument: infer Intersection) => void // tslint:disable-line: no-unused ? Intersection diff --git a/test/__snapshots__/plugin.test.ts.snap b/test/__snapshots__/plugin.test.ts.snap new file mode 100644 index 000000000..321878185 --- /dev/null +++ b/test/__snapshots__/plugin.test.ts.snap @@ -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, + }, + ], +} +`; diff --git a/test/plugin.test.ts b/test/plugin.test.ts index b0ceb6a47..dc5ae75c7 100644 --- a/test/plugin.test.ts +++ b/test/plugin.test.ts @@ -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", () => { + 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"); }); @@ -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");