mirror of
https://github.com/status-im/sourcecred.git
synced 2025-02-13 13:06:32 +00:00
Expose a ReferenceDetector to createPluginGraphs (#1673)
Eventually all plugins are expected to use the ReferenceDetector. This commit composes the Github and Discourse detectors we can create, for now exposing it as unused to `createPluginGraphs`.
This commit is contained in:
parent
68aac1f48d
commit
f260569605
@ -68,6 +68,7 @@ export class LoadContext {
|
|||||||
+_declarations = PluginLoaders.declarations;
|
+_declarations = PluginLoaders.declarations;
|
||||||
+_updateMirror = PluginLoaders.updateMirror;
|
+_updateMirror = PluginLoaders.updateMirror;
|
||||||
+_createPluginGraphs = PluginLoaders.createPluginGraphs;
|
+_createPluginGraphs = PluginLoaders.createPluginGraphs;
|
||||||
|
+_createReferenceDetector = PluginLoaders.createReferenceDetector;
|
||||||
+_contractPluginGraphs = PluginLoaders.contractPluginGraphs;
|
+_contractPluginGraphs = PluginLoaders.contractPluginGraphs;
|
||||||
+_overrideWeights = WeightedGraph.overrideWeights;
|
+_overrideWeights = WeightedGraph.overrideWeights;
|
||||||
+_computeTask = ComputeFunction.computeTask;
|
+_computeTask = ComputeFunction.computeTask;
|
||||||
@ -98,11 +99,17 @@ export class LoadContext {
|
|||||||
this._options,
|
this._options,
|
||||||
project
|
project
|
||||||
);
|
);
|
||||||
const pluginGraphs = await this._createPluginGraphs(
|
const referenceDetector = await this._createReferenceDetector(
|
||||||
this._pluginLoaders,
|
this._pluginLoaders,
|
||||||
this._options,
|
this._options,
|
||||||
cachedProject
|
cachedProject
|
||||||
);
|
);
|
||||||
|
const pluginGraphs = await this._createPluginGraphs(
|
||||||
|
this._pluginLoaders,
|
||||||
|
this._options,
|
||||||
|
cachedProject,
|
||||||
|
referenceDetector
|
||||||
|
);
|
||||||
const contractedGraph = await this._contractPluginGraphs(
|
const contractedGraph = await this._contractPluginGraphs(
|
||||||
this._pluginLoaders,
|
this._pluginLoaders,
|
||||||
pluginGraphs
|
pluginGraphs
|
||||||
|
@ -9,6 +9,7 @@ import {LoadContext} from "./loadContext";
|
|||||||
|
|
||||||
const fakes = {
|
const fakes = {
|
||||||
declarations: ({fake: "declarations"}: any),
|
declarations: ({fake: "declarations"}: any),
|
||||||
|
referenceDetector: ({fake: "referenceDetector"}: any),
|
||||||
pluginGraphs: ({fake: "pluginGraphs"}: any),
|
pluginGraphs: ({fake: "pluginGraphs"}: any),
|
||||||
contractedGraph: ({fake: "contractedGraph"}: any),
|
contractedGraph: ({fake: "contractedGraph"}: any),
|
||||||
weightedGraph: ({fake: "weightedGraph"}: any),
|
weightedGraph: ({fake: "weightedGraph"}: any),
|
||||||
@ -44,6 +45,10 @@ const mockProxyMethods = (
|
|||||||
.proxyMethod("updateMirror")
|
.proxyMethod("updateMirror")
|
||||||
.mockResolvedValueOnce({project, cache}),
|
.mockResolvedValueOnce({project, cache}),
|
||||||
|
|
||||||
|
createReferenceDetector: spyBuilder
|
||||||
|
.proxyMethod("createReferenceDetector")
|
||||||
|
.mockResolvedValueOnce(fakes.referenceDetector),
|
||||||
|
|
||||||
createPluginGraphs: spyBuilder
|
createPluginGraphs: spyBuilder
|
||||||
.proxyMethod("createPluginGraphs")
|
.proxyMethod("createPluginGraphs")
|
||||||
.mockResolvedValueOnce(fakes.pluginGraphs),
|
.mockResolvedValueOnce(fakes.pluginGraphs),
|
||||||
@ -98,6 +103,7 @@ describe("src/backend/loadContext", () => {
|
|||||||
// Methods
|
// Methods
|
||||||
_declarations: expect.anything(),
|
_declarations: expect.anything(),
|
||||||
_updateMirror: expect.anything(),
|
_updateMirror: expect.anything(),
|
||||||
|
_createReferenceDetector: expect.anything(),
|
||||||
_createPluginGraphs: expect.anything(),
|
_createPluginGraphs: expect.anything(),
|
||||||
_contractPluginGraphs: expect.anything(),
|
_contractPluginGraphs: expect.anything(),
|
||||||
_overrideWeights: expect.anything(),
|
_overrideWeights: expect.anything(),
|
||||||
@ -140,11 +146,17 @@ describe("src/backend/loadContext", () => {
|
|||||||
expectedEnv,
|
expectedEnv,
|
||||||
project
|
project
|
||||||
);
|
);
|
||||||
expect(spies.createPluginGraphs).toBeCalledWith(
|
expect(spies.createReferenceDetector).toBeCalledWith(
|
||||||
loadContext._pluginLoaders,
|
loadContext._pluginLoaders,
|
||||||
expectedEnv,
|
expectedEnv,
|
||||||
cachedProject
|
cachedProject
|
||||||
);
|
);
|
||||||
|
expect(spies.createPluginGraphs).toBeCalledWith(
|
||||||
|
loadContext._pluginLoaders,
|
||||||
|
expectedEnv,
|
||||||
|
cachedProject,
|
||||||
|
fakes.referenceDetector
|
||||||
|
);
|
||||||
expect(spies.contractPluginGraphs).toBeCalledWith(
|
expect(spies.contractPluginGraphs).toBeCalledWith(
|
||||||
loadContext._pluginLoaders,
|
loadContext._pluginLoaders,
|
||||||
fakes.pluginGraphs
|
fakes.pluginGraphs
|
||||||
|
@ -10,6 +10,10 @@ import {type GithubToken} from "../plugins/github/token";
|
|||||||
import {type Loader as GithubLoader} from "../plugins/github/loader";
|
import {type Loader as GithubLoader} from "../plugins/github/loader";
|
||||||
import {type Loader as IdentityLoader} from "../plugins/identity/loader";
|
import {type Loader as IdentityLoader} from "../plugins/identity/loader";
|
||||||
import {type Loader as DiscourseLoader} from "../plugins/discourse/loader";
|
import {type Loader as DiscourseLoader} from "../plugins/discourse/loader";
|
||||||
|
import {
|
||||||
|
type ReferenceDetector,
|
||||||
|
CascadingReferenceDetector,
|
||||||
|
} from "../core/references";
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* A type combining all known plugin Loader interfaces.
|
* A type combining all known plugin Loader interfaces.
|
||||||
@ -97,13 +101,41 @@ export async function updateMirror(
|
|||||||
return {project, cache};
|
return {project, cache};
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Creates a ReferenceDetector composing all plugin reference detectors
|
||||||
|
* requested by the project.
|
||||||
|
*/
|
||||||
|
export async function createReferenceDetector(
|
||||||
|
{github, discourse}: $Shape<PluginLoaders>,
|
||||||
|
{githubToken}: GraphEnv,
|
||||||
|
{cache, project}: CachedProject
|
||||||
|
): Promise<ReferenceDetector> {
|
||||||
|
const refs: ReferenceDetector[] = [];
|
||||||
|
if (project.repoIds.length) {
|
||||||
|
// TODO: similar to create graph, rather not depend on the token (#1580).
|
||||||
|
if (!githubToken) {
|
||||||
|
throw new Error("Tried to load GitHub, but no GitHub token set");
|
||||||
|
}
|
||||||
|
refs.push(
|
||||||
|
await github.referenceDetector(project.repoIds, githubToken, cache)
|
||||||
|
);
|
||||||
|
}
|
||||||
|
if (project.discourseServer) {
|
||||||
|
refs.push(
|
||||||
|
await discourse.referenceDetector(project.discourseServer, cache)
|
||||||
|
);
|
||||||
|
}
|
||||||
|
return new CascadingReferenceDetector(refs);
|
||||||
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Creates PluginGraphs containing all plugins requested by the Project.
|
* Creates PluginGraphs containing all plugins requested by the Project.
|
||||||
*/
|
*/
|
||||||
export async function createPluginGraphs(
|
export async function createPluginGraphs(
|
||||||
{github, discourse}: PluginLoaders,
|
{github, discourse}: PluginLoaders,
|
||||||
{githubToken}: GraphEnv,
|
{githubToken}: GraphEnv,
|
||||||
{cache, project}: CachedProject
|
{cache, project}: CachedProject,
|
||||||
|
_unused_referenceDetector: ReferenceDetector
|
||||||
): Promise<PluginGraphs> {
|
): Promise<PluginGraphs> {
|
||||||
const tasks: Promise<WeightedGraphT>[] = [];
|
const tasks: Promise<WeightedGraphT>[] = [];
|
||||||
if (project.discourseServer) {
|
if (project.discourseServer) {
|
||||||
|
@ -1,6 +1,10 @@
|
|||||||
// @flow
|
// @flow
|
||||||
|
|
||||||
import {type CacheProvider} from "./cache";
|
import {type CacheProvider} from "./cache";
|
||||||
|
import {
|
||||||
|
type ReferenceDetector,
|
||||||
|
CascadingReferenceDetector,
|
||||||
|
} from "../core/references";
|
||||||
import * as WeightedGraph from "../core/weightedGraph";
|
import * as WeightedGraph from "../core/weightedGraph";
|
||||||
import {node as graphNode} from "../core/graphTestUtil";
|
import {node as graphNode} from "../core/graphTestUtil";
|
||||||
import {createProject} from "../core/project";
|
import {createProject} from "../core/project";
|
||||||
@ -23,7 +27,9 @@ const mockGraphs = {
|
|||||||
|
|
||||||
const fakes = {
|
const fakes = {
|
||||||
githubDeclaration: ({fake: "githubDeclaration"}: any),
|
githubDeclaration: ({fake: "githubDeclaration"}: any),
|
||||||
|
githubReferences: ({fake: "githubReferences"}: any),
|
||||||
discourseDeclaration: ({fake: "discourseDeclaration"}: any),
|
discourseDeclaration: ({fake: "discourseDeclaration"}: any),
|
||||||
|
discourseReferences: ({fake: "discourseReferences"}: any),
|
||||||
identityDeclaration: ({fake: "identityDeclaration"}: any),
|
identityDeclaration: ({fake: "identityDeclaration"}: any),
|
||||||
};
|
};
|
||||||
|
|
||||||
@ -31,15 +37,21 @@ const mockCacheProvider = (): CacheProvider => ({
|
|||||||
database: jest.fn(),
|
database: jest.fn(),
|
||||||
});
|
});
|
||||||
|
|
||||||
|
const mockReferenceDetector = (): ReferenceDetector => ({
|
||||||
|
addressFromUrl: jest.fn(),
|
||||||
|
});
|
||||||
|
|
||||||
const mockPluginLoaders = () => ({
|
const mockPluginLoaders = () => ({
|
||||||
github: {
|
github: {
|
||||||
declaration: jest.fn().mockReturnValue(fakes.githubDeclaration),
|
declaration: jest.fn().mockReturnValue(fakes.githubDeclaration),
|
||||||
updateMirror: jest.fn(),
|
updateMirror: jest.fn(),
|
||||||
|
referenceDetector: jest.fn().mockResolvedValue(fakes.githubReferences),
|
||||||
createGraph: jest.fn().mockResolvedValue(mockGraphs.github),
|
createGraph: jest.fn().mockResolvedValue(mockGraphs.github),
|
||||||
},
|
},
|
||||||
discourse: {
|
discourse: {
|
||||||
declaration: jest.fn().mockReturnValue(fakes.discourseDeclaration),
|
declaration: jest.fn().mockReturnValue(fakes.discourseDeclaration),
|
||||||
updateMirror: jest.fn(),
|
updateMirror: jest.fn(),
|
||||||
|
referenceDetector: jest.fn().mockResolvedValue(fakes.discourseReferences),
|
||||||
createGraph: jest.fn().mockResolvedValue(mockGraphs.discourse),
|
createGraph: jest.fn().mockResolvedValue(mockGraphs.discourse),
|
||||||
},
|
},
|
||||||
identity: {
|
identity: {
|
||||||
@ -185,6 +197,7 @@ describe("src/backend/pluginLoaders", () => {
|
|||||||
describe("createPluginGraphs", () => {
|
describe("createPluginGraphs", () => {
|
||||||
it("should create discourse graph", async () => {
|
it("should create discourse graph", async () => {
|
||||||
// Given
|
// Given
|
||||||
|
const references = mockReferenceDetector();
|
||||||
const loaders = mockPluginLoaders();
|
const loaders = mockPluginLoaders();
|
||||||
const cache = mockCacheProvider();
|
const cache = mockCacheProvider();
|
||||||
const githubToken = null;
|
const githubToken = null;
|
||||||
@ -198,7 +211,8 @@ describe("src/backend/pluginLoaders", () => {
|
|||||||
const pluginGraphs = await PluginLoaders.createPluginGraphs(
|
const pluginGraphs = await PluginLoaders.createPluginGraphs(
|
||||||
loaders,
|
loaders,
|
||||||
{githubToken},
|
{githubToken},
|
||||||
cachedProject
|
cachedProject,
|
||||||
|
references
|
||||||
);
|
);
|
||||||
|
|
||||||
// Then
|
// Then
|
||||||
@ -216,6 +230,7 @@ describe("src/backend/pluginLoaders", () => {
|
|||||||
|
|
||||||
it("fail when missing GithubToken", async () => {
|
it("fail when missing GithubToken", async () => {
|
||||||
// Given
|
// Given
|
||||||
|
const references = mockReferenceDetector();
|
||||||
const loaders = mockPluginLoaders();
|
const loaders = mockPluginLoaders();
|
||||||
const cache = mockCacheProvider();
|
const cache = mockCacheProvider();
|
||||||
const githubToken = null;
|
const githubToken = null;
|
||||||
@ -229,7 +244,8 @@ describe("src/backend/pluginLoaders", () => {
|
|||||||
const p = PluginLoaders.createPluginGraphs(
|
const p = PluginLoaders.createPluginGraphs(
|
||||||
loaders,
|
loaders,
|
||||||
{githubToken},
|
{githubToken},
|
||||||
cachedProject
|
cachedProject,
|
||||||
|
references
|
||||||
);
|
);
|
||||||
|
|
||||||
// Then
|
// Then
|
||||||
@ -240,6 +256,7 @@ describe("src/backend/pluginLoaders", () => {
|
|||||||
|
|
||||||
it("should create github graph", async () => {
|
it("should create github graph", async () => {
|
||||||
// Given
|
// Given
|
||||||
|
const references = mockReferenceDetector();
|
||||||
const loaders = mockPluginLoaders();
|
const loaders = mockPluginLoaders();
|
||||||
const cache = mockCacheProvider();
|
const cache = mockCacheProvider();
|
||||||
const githubToken = exampleGithubToken;
|
const githubToken = exampleGithubToken;
|
||||||
@ -253,7 +270,8 @@ describe("src/backend/pluginLoaders", () => {
|
|||||||
const pluginGraphs = await PluginLoaders.createPluginGraphs(
|
const pluginGraphs = await PluginLoaders.createPluginGraphs(
|
||||||
loaders,
|
loaders,
|
||||||
{githubToken},
|
{githubToken},
|
||||||
cachedProject
|
cachedProject,
|
||||||
|
references
|
||||||
);
|
);
|
||||||
|
|
||||||
// Then
|
// Then
|
||||||
@ -271,6 +289,35 @@ describe("src/backend/pluginLoaders", () => {
|
|||||||
});
|
});
|
||||||
});
|
});
|
||||||
|
|
||||||
|
describe("createReferenceDetector", () => {
|
||||||
|
it("should create a CascadingReferenceDetector", async () => {
|
||||||
|
// Given
|
||||||
|
const loaders = mockPluginLoaders();
|
||||||
|
const cache = mockCacheProvider();
|
||||||
|
const githubToken = exampleGithubToken;
|
||||||
|
const project = createProject({
|
||||||
|
id: "has-github-and-discourse",
|
||||||
|
discourseServer: {serverUrl: "http://foo.bar"},
|
||||||
|
repoIds: [exampleRepoId],
|
||||||
|
});
|
||||||
|
const cachedProject = ({project, cache}: any);
|
||||||
|
|
||||||
|
// When
|
||||||
|
const references = await PluginLoaders.createReferenceDetector(
|
||||||
|
loaders,
|
||||||
|
{githubToken},
|
||||||
|
cachedProject
|
||||||
|
);
|
||||||
|
|
||||||
|
// Then
|
||||||
|
expect(references).toBeInstanceOf(CascadingReferenceDetector);
|
||||||
|
expect(((references: any): CascadingReferenceDetector).refs).toEqual([
|
||||||
|
fakes.githubReferences,
|
||||||
|
fakes.discourseReferences,
|
||||||
|
]);
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
describe("contractPluginGraphs", () => {
|
describe("contractPluginGraphs", () => {
|
||||||
it("should only merge graphs when no identities are defined", async () => {
|
it("should only merge graphs when no identities are defined", async () => {
|
||||||
// Given
|
// Given
|
||||||
|
@ -4,11 +4,13 @@ import base64url from "base64url";
|
|||||||
import {TaskReporter} from "../../util/taskReporter";
|
import {TaskReporter} from "../../util/taskReporter";
|
||||||
import {type CacheProvider} from "../../backend/cache";
|
import {type CacheProvider} from "../../backend/cache";
|
||||||
import {type WeightedGraph} from "../../core/weightedGraph";
|
import {type WeightedGraph} from "../../core/weightedGraph";
|
||||||
|
import {type ReferenceDetector} from "../../core/references";
|
||||||
import {type PluginDeclaration} from "../../analysis/pluginDeclaration";
|
import {type PluginDeclaration} from "../../analysis/pluginDeclaration";
|
||||||
import {type DiscourseServer} from "./server";
|
import {type DiscourseServer} from "./server";
|
||||||
import {Mirror} from "./mirror";
|
import {Mirror} from "./mirror";
|
||||||
import {SqliteMirrorRepository} from "./mirrorRepository";
|
import {SqliteMirrorRepository} from "./mirrorRepository";
|
||||||
import {weightsForDeclaration} from "../../analysis/pluginDeclaration";
|
import {weightsForDeclaration} from "../../analysis/pluginDeclaration";
|
||||||
|
import {DiscourseReferenceDetector} from "./referenceDetector";
|
||||||
import {createGraph as _createGraph} from "./createGraph";
|
import {createGraph as _createGraph} from "./createGraph";
|
||||||
import {declaration} from "./declaration";
|
import {declaration} from "./declaration";
|
||||||
import {Fetcher} from "./fetch";
|
import {Fetcher} from "./fetch";
|
||||||
@ -20,6 +22,10 @@ export interface Loader {
|
|||||||
cache: CacheProvider,
|
cache: CacheProvider,
|
||||||
reporter: TaskReporter
|
reporter: TaskReporter
|
||||||
): Promise<void>;
|
): Promise<void>;
|
||||||
|
referenceDetector(
|
||||||
|
server: DiscourseServer,
|
||||||
|
cache: CacheProvider
|
||||||
|
): Promise<ReferenceDetector>;
|
||||||
createGraph(
|
createGraph(
|
||||||
server: DiscourseServer,
|
server: DiscourseServer,
|
||||||
cache: CacheProvider
|
cache: CacheProvider
|
||||||
@ -29,6 +35,7 @@ export interface Loader {
|
|||||||
export default ({
|
export default ({
|
||||||
declaration: () => declaration,
|
declaration: () => declaration,
|
||||||
updateMirror,
|
updateMirror,
|
||||||
|
referenceDetector,
|
||||||
createGraph,
|
createGraph,
|
||||||
}: Loader);
|
}: Loader);
|
||||||
|
|
||||||
@ -44,6 +51,14 @@ export async function updateMirror(
|
|||||||
await mirror.update(reporter);
|
await mirror.update(reporter);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
export async function referenceDetector(
|
||||||
|
{serverUrl}: DiscourseServer,
|
||||||
|
cache: CacheProvider
|
||||||
|
): Promise<ReferenceDetector> {
|
||||||
|
const repo = await repository(cache, serverUrl);
|
||||||
|
return new DiscourseReferenceDetector(repo);
|
||||||
|
}
|
||||||
|
|
||||||
export async function createGraph(
|
export async function createGraph(
|
||||||
{serverUrl}: DiscourseServer,
|
{serverUrl}: DiscourseServer,
|
||||||
cache: CacheProvider
|
cache: CacheProvider
|
||||||
|
@ -3,6 +3,7 @@
|
|||||||
import {TaskReporter} from "../../util/taskReporter";
|
import {TaskReporter} from "../../util/taskReporter";
|
||||||
import {type CacheProvider} from "../../backend/cache";
|
import {type CacheProvider} from "../../backend/cache";
|
||||||
import {type WeightedGraph} from "../../core/weightedGraph";
|
import {type WeightedGraph} from "../../core/weightedGraph";
|
||||||
|
import {type ReferenceDetector} from "../../core/references";
|
||||||
import {type PluginDeclaration} from "../../analysis/pluginDeclaration";
|
import {type PluginDeclaration} from "../../analysis/pluginDeclaration";
|
||||||
import {type GithubToken} from "./token";
|
import {type GithubToken} from "./token";
|
||||||
import {Graph} from "../../core/graph";
|
import {Graph} from "../../core/graph";
|
||||||
@ -15,6 +16,7 @@ import {
|
|||||||
default as fetchGithubRepo,
|
default as fetchGithubRepo,
|
||||||
fetchGithubRepoFromCache,
|
fetchGithubRepoFromCache,
|
||||||
} from "./fetchGithubRepo";
|
} from "./fetchGithubRepo";
|
||||||
|
import {fromRelationalViews as referenceDetectorFromRelationalViews} from "./referenceDetector";
|
||||||
|
|
||||||
export interface Loader {
|
export interface Loader {
|
||||||
declaration(): PluginDeclaration;
|
declaration(): PluginDeclaration;
|
||||||
@ -24,6 +26,11 @@ export interface Loader {
|
|||||||
cache: CacheProvider,
|
cache: CacheProvider,
|
||||||
reporter: TaskReporter
|
reporter: TaskReporter
|
||||||
): Promise<void>;
|
): Promise<void>;
|
||||||
|
referenceDetector(
|
||||||
|
repoIds: $ReadOnlyArray<RepoId>,
|
||||||
|
token: GithubToken,
|
||||||
|
cache: CacheProvider
|
||||||
|
): Promise<ReferenceDetector>;
|
||||||
createGraph(
|
createGraph(
|
||||||
repoIds: $ReadOnlyArray<RepoId>,
|
repoIds: $ReadOnlyArray<RepoId>,
|
||||||
token: GithubToken,
|
token: GithubToken,
|
||||||
@ -33,6 +40,7 @@ export interface Loader {
|
|||||||
|
|
||||||
export default ({
|
export default ({
|
||||||
declaration: () => declaration,
|
declaration: () => declaration,
|
||||||
|
referenceDetector,
|
||||||
updateMirror,
|
updateMirror,
|
||||||
createGraph,
|
createGraph,
|
||||||
}: Loader);
|
}: Loader);
|
||||||
@ -54,6 +62,21 @@ export async function updateMirror(
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
export async function referenceDetector(
|
||||||
|
repoIds: $ReadOnlyArray<RepoId>,
|
||||||
|
token: GithubToken,
|
||||||
|
cache: CacheProvider
|
||||||
|
): Promise<ReferenceDetector> {
|
||||||
|
const rvs = [];
|
||||||
|
for (const repoId of repoIds) {
|
||||||
|
const repo = await fetchGithubRepoFromCache(repoId, {token, cache});
|
||||||
|
const rv = new RelationalView();
|
||||||
|
rv.addRepository(repo);
|
||||||
|
rvs.push(rv);
|
||||||
|
}
|
||||||
|
return referenceDetectorFromRelationalViews(rvs);
|
||||||
|
}
|
||||||
|
|
||||||
export async function createGraph(
|
export async function createGraph(
|
||||||
repoIds: $ReadOnlyArray<RepoId>,
|
repoIds: $ReadOnlyArray<RepoId>,
|
||||||
token: GithubToken,
|
token: GithubToken,
|
||||||
|
Loading…
x
Reference in New Issue
Block a user